Skip to content

Commit

Permalink
Adds support for Jira Server (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
mediabounds authored Feb 2, 2023
1 parent 064e3f1 commit ccc4fa4
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 8 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ A Stream Deck plugin for viewing the number of Jira issues matching a JQL query
* The button opens each matching issue in a separate browser window
* Many customization options for how the badge is displayed
* Allows for custom icons to be set
* Supports Jira Cloud and Jira Server (8.14 and later)

## Installation
### Preferred: Stream Deck Store
Expand All @@ -20,8 +21,11 @@ https://apps.elgato.com/plugins/com.mediabounds.streamdeck.jira
## Configuration
### Global settings
* **Domain** -- the product URL for your Jira organization (i.e. `organization.atlassian.net`)
* **Type** -- whether your Jira instance is Jira Cloud or Jira Server
* **Email** -- the email address for your Atlassian account
* **API Token** -- an API token for your account which can be created at <https://id.atlassian.com/manage-profile/security/api-tokens>.
* **API Token** -- an API token for your account
* For Jira Cloud, this can be created at <https://id.atlassian.com/manage-profile/security/api-tokens>
* For Jira Server, you'll need a [Personal Access Token](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html)

### JQL Result action
In addition to the **Global settings**, the JQL Result action also requires:
Expand Down
9 changes: 9 additions & 0 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,12 @@ export class BasicAuth implements Authenticator {
}
}

/**
* Authenticates requests using a bearer token.
*/
export class TokenAuth implements Authenticator {
constructor(private token: string) {}
public getAuthorizationHeader(): string {
return `Bearer ${this.token}`;
}
}
34 changes: 34 additions & 0 deletions src/JiraConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Client, { Authenticator, BasicAuth, TokenAuth } from "./Client";
import { DefaultPluginSettings } from "./JiraPluginSettings";

/**
* Convenience class for getting connected to Jira.
*/
export class JiraConnection {
/**
* Creates a new Client for making API requests.
*
* @param settings - Connection settings for the client.
* @returns A configured Client.
*/
public static getClient(settings: DefaultPluginSettings): Client {
const {domain, email: username, token: key, strategy} = settings;

if (!domain) {
throw new Error('A domain must be set');
}

if (!key) {
throw new Error('An API token must be set');
}

let authenticator: Authenticator;
if (strategy === 'PAT') {
authenticator = new TokenAuth(key);
} else {
authenticator = new BasicAuth(username, key);
}

return new Client(`https://${domain}`, authenticator);
}
}
11 changes: 11 additions & 0 deletions src/JiraPluginSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,15 @@ export interface DefaultPluginSettings {
* @see https://id.atlassian.com/manage-profile/security/api-tokens
*/
token: string;

/**
* The authentication strategy to use.
*
* API tokens are for JIRA cloud.
* @see https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/
*
* Personal access tokens (PAT) are for JIRA server.
* @see https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html
*/
strategy: 'APIToken' | 'PAT'
}
9 changes: 4 additions & 5 deletions src/actions/Query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DidReceiveSettingsEvent, KeyDownEvent } from "@fnando/streamdeck";
import Client, { BasicAuth } from "../Client";
import Icon, { BadgeOptions } from "../Icon";
import { JiraConnection } from "../JiraConnection";
import { BadgeType, JQLQuerySettings } from "../JiraPluginSettings";
import { PollingErrorEvent, PollingResponseEvent } from "../PollingClient";
import PollingAction, { ActionPollingContext } from "./PollingAction";
Expand Down Expand Up @@ -66,7 +66,7 @@ class Query extends PollingAction<SearchResponse, JQLQuerySettings> {
* {@inheritDoc}
*/
protected async getResponse(context: ActionPollingContext<JQLQuerySettings>): Promise<SearchResponse> {
const {domain, email: username, token: password, jql} = context.settings;
const {domain, jql} = context.settings;

if (!domain || !jql) {
return {
Expand All @@ -75,10 +75,9 @@ class Query extends PollingAction<SearchResponse, JQLQuerySettings> {
};
}

const client = new Client(`https://${domain}`, new BasicAuth(username, password));

const client = JiraConnection.getClient(context.settings);
const response = await client.request<SearchResponse>({
endpoint: 'rest/api/3/search',
endpoint: 'rest/api/latest/search',
query: {
jql: jql,
},
Expand Down
13 changes: 12 additions & 1 deletion src/inspector.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@
<input class="sdpi-item-value" type="text" id="domain" placeholder="your-domain.atlassian.net" pattern="([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}" global>
</div>

<div class="sdpi-item">
<div class="sdpi-item-label">Type</div>
<select class="sdpi-item-value" id="token-type">
<option value="APIToken" selected>Jira Cloud</option>
<option value="PAT">Jira Server</option>
</select>
</div>

<div class="sdpi-item">
<div class="sdpi-item-label">Email</div>
<input class="sdpi-item-value" type="email" id="email" global>
</div>

<div class="sdpi-item">
<div class="sdpi-item-label"><a href="https://id.atlassian.com/manage-profile/security/api-tokens" target="_blank">API Token</a></div>
<div class="sdpi-item-label">
<a href="https://id.atlassian.com/manage-profile/security/api-tokens" target="_blank" x-token-type="APIToken">API Token</a>
<a href="https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html" target="_blank" x-token-type="PAT" hidden>Access Token</a>
</div>
<input class="sdpi-item-value" type="password" id="token" pattern="\w{24,}" global>
</div>

Expand Down
13 changes: 12 additions & 1 deletion src/inspectors/Query.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@
<input class="sdpi-item-value" type="text" id="domain" placeholder="your-domain.atlassian.net" pattern="([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}" global>
</div>

<div class="sdpi-item">
<div class="sdpi-item-label">Type</div>
<select class="sdpi-item-value" id="token-type">
<option value="APIToken" selected>Jira Cloud</option>
<option value="PAT">Jira Server</option>
</select>
</div>

<div class="sdpi-item">
<div class="sdpi-item-label">Email</div>
<input class="sdpi-item-value" type="email" id="email" global>
</div>

<div class="sdpi-item">
<div class="sdpi-item-label"><a href="https://id.atlassian.com/manage-profile/security/api-tokens" target="_blank">API Token</a></div>
<div class="sdpi-item-label">
<a href="https://id.atlassian.com/manage-profile/security/api-tokens" target="_blank" x-token-type="APIToken">API Token</a>
<a href="https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html" target="_blank" x-token-type="PAT" hidden>Access Token</a>
</div>
<input class="sdpi-item-value" type="password" id="token" pattern="\w{24,}" global>
</div>

Expand Down
6 changes: 6 additions & 0 deletions src/inspectors/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class QueryActionPropertyInspector extends PollingActionInspector<JQLQuerySettin
private domain = document.getElementById('domain') as HTMLInputElement;
private email = document.getElementById('email') as HTMLInputElement;
private token = document.getElementById('token') as HTMLInputElement;
private tokenType = document.getElementById('token-type') as HTMLSelectElement;
private jql = document.getElementById('jql') as HTMLTextAreaElement;
private status = document.getElementById('status-display');
private keyAction = document.getElementById('key-action') as HTMLSelectElement;
Expand Down Expand Up @@ -41,9 +42,11 @@ class QueryActionPropertyInspector extends PollingActionInspector<JQLQuerySettin

// Base settings.
this.domain.value = settings.domain;
this.tokenType.value = settings.strategy;
this.email.value = settings.email;
this.token.value = settings.token;
this.jql.value = settings.jql;
document.querySelectorAll('[x-token-type]').forEach(el => (<HTMLElement>el).hidden = el.getAttribute('x-token-type') != this.tokenType.value);

// Action settings.
this.keyActionLimit.hidden = true;
Expand Down Expand Up @@ -119,6 +122,7 @@ class QueryActionPropertyInspector extends PollingActionInspector<JQLQuerySettin
.trim(),
email: this.email.value.trim(),
token: this.token.value.trim(),
strategy: <'APIToken'|'PAT'>this.tokenType.value,
jql: this.jql.value.trim(),
keyAction: this.getKeyAction(),
pollingDelay: this.settings.pollingDelay,
Expand All @@ -136,6 +140,7 @@ class QueryActionPropertyInspector extends PollingActionInspector<JQLQuerySettin
domain: settings.domain,
email: settings.email,
token: settings.token,
strategy: settings.strategy,
});
}

Expand All @@ -148,6 +153,7 @@ class QueryActionPropertyInspector extends PollingActionInspector<JQLQuerySettin
domain: this.globalSettings.domain ?? '',
email: this.globalSettings.email ?? '',
token: this.globalSettings.token ?? '',
strategy: this.globalSettings.strategy ?? 'APIToken',
jql: '',
keyAction: {
limit: 5,
Expand Down

0 comments on commit ccc4fa4

Please sign in to comment.