mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 21:19:13 +00:00
Add support for Jira version events (#534)
- Support version created/updated/released events - Look up project ID if missing when subscribing to version events - Properly format version event notices - Prioritize project URL over ID in debug strings
This commit is contained in:
parent
d82e0d7d91
commit
505c083f5f
1
changelog.d/534.feature
Normal file
1
changelog.d/534.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Support Jira version events.
|
@ -40,3 +40,7 @@ This connection supports sending messages when the following actions happen on t
|
|||||||
- issue
|
- issue
|
||||||
- issue_created
|
- issue_created
|
||||||
- issue_updated
|
- issue_updated
|
||||||
|
- version
|
||||||
|
- version_created
|
||||||
|
- version_updated
|
||||||
|
- version_released
|
@ -11,7 +11,7 @@ import { IBridgeStorageProvider } from "./Stores/StorageProvider";
|
|||||||
import { IConnection, GitHubDiscussionSpace, GitHubDiscussionConnection, GitHubUserSpace, JiraProjectConnection, GitLabRepoConnection,
|
import { IConnection, GitHubDiscussionSpace, GitHubDiscussionConnection, GitHubUserSpace, JiraProjectConnection, GitLabRepoConnection,
|
||||||
GitHubIssueConnection, GitHubProjectConnection, GitHubRepoConnection, GitLabIssueConnection, FigmaFileConnection, FeedConnection, GenericHookConnection } from "./Connections";
|
GitHubIssueConnection, GitHubProjectConnection, GitHubRepoConnection, GitLabIssueConnection, FigmaFileConnection, FeedConnection, GenericHookConnection } from "./Connections";
|
||||||
import { IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookNoteEvent, IGitLabWebhookPushEvent, IGitLabWebhookReleaseEvent, IGitLabWebhookTagPushEvent, IGitLabWebhookWikiPageEvent } from "./Gitlab/WebhookTypes";
|
import { IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookNoteEvent, IGitLabWebhookPushEvent, IGitLabWebhookReleaseEvent, IGitLabWebhookTagPushEvent, IGitLabWebhookWikiPageEvent } from "./Gitlab/WebhookTypes";
|
||||||
import { JiraIssueEvent, JiraIssueUpdatedEvent } from "./Jira/WebhookTypes";
|
import { JiraIssueEvent, JiraIssueUpdatedEvent, JiraVersionEvent } from "./Jira/WebhookTypes";
|
||||||
import { JiraOAuthResult } from "./Jira/Types";
|
import { JiraOAuthResult } from "./Jira/Types";
|
||||||
import { MatrixEvent, MatrixMemberContent, MatrixMessageContent } from "./MatrixEvent";
|
import { MatrixEvent, MatrixMemberContent, MatrixMessageContent } from "./MatrixEvent";
|
||||||
import { MemoryStorageProvider } from "./Stores/MemoryStorageProvider";
|
import { MemoryStorageProvider } from "./Stores/MemoryStorageProvider";
|
||||||
@ -516,6 +516,14 @@ export class Bridge {
|
|||||||
(c, data) => c.onJiraIssueUpdated(data),
|
(c, data) => c.onJiraIssueUpdated(data),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (const event of ["created", "updated", "released"]) {
|
||||||
|
this.bindHandlerToQueue<JiraVersionEvent, JiraProjectConnection>(
|
||||||
|
`jira.version_${event}`,
|
||||||
|
(data) => connManager.getConnectionsForJiraVersion(data.version),
|
||||||
|
(c, data) => c.onJiraVersionEvent(data),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.queue.on<JiraOAuthRequestCloud|JiraOAuthRequestOnPrem>("jira.oauth.response", async (msg) => {
|
this.queue.on<JiraOAuthRequestCloud|JiraOAuthRequestOnPrem>("jira.oauth.response", async (msg) => {
|
||||||
if (!this.config.jira || !this.tokenStore.jiraOAuth) {
|
if (!this.config.jira || !this.tokenStore.jiraOAuth) {
|
||||||
throw Error('Cannot handle, JIRA oauth support not enabled');
|
throw Error('Cannot handle, JIRA oauth support not enabled');
|
||||||
|
@ -10,7 +10,7 @@ import { BridgeConfig, BridgePermissionLevel, GitLabInstance } from "./Config/Co
|
|||||||
import { ConnectionDeclarations, GenericHookConnection, GitHubDiscussionConnection, GitHubDiscussionSpace, GitHubIssueConnection, GitHubProjectConnection, GitHubRepoConnection, GitHubUserSpace, GitLabIssueConnection, GitLabRepoConnection, IConnection, JiraProjectConnection } from "./Connections";
|
import { ConnectionDeclarations, GenericHookConnection, GitHubDiscussionConnection, GitHubDiscussionSpace, GitHubIssueConnection, GitHubProjectConnection, GitHubRepoConnection, GitHubUserSpace, GitLabIssueConnection, GitLabRepoConnection, IConnection, JiraProjectConnection } from "./Connections";
|
||||||
import { GithubInstance } from "./Github/GithubInstance";
|
import { GithubInstance } from "./Github/GithubInstance";
|
||||||
import { GitLabClient } from "./Gitlab/Client";
|
import { GitLabClient } from "./Gitlab/Client";
|
||||||
import { JiraProject } from "./Jira/Types";
|
import { JiraProject, JiraVersion } from "./Jira/Types";
|
||||||
import { Logger } from "matrix-appservice-bridge";
|
import { Logger } from "matrix-appservice-bridge";
|
||||||
import { MessageSenderClient } from "./MatrixSender";
|
import { MessageSenderClient } from "./MatrixSender";
|
||||||
import { GetConnectionTypeResponseItem } from "./provisioning/api";
|
import { GetConnectionTypeResponseItem } from "./provisioning/api";
|
||||||
@ -213,6 +213,10 @@ export class ConnectionManager extends EventEmitter {
|
|||||||
return this.connections.filter((c) => (c instanceof JiraProjectConnection && c.interestedInProject(project))) as JiraProjectConnection[];
|
return this.connections.filter((c) => (c instanceof JiraProjectConnection && c.interestedInProject(project))) as JiraProjectConnection[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getConnectionsForJiraVersion(version: JiraVersion): JiraProjectConnection[] {
|
||||||
|
return this.connections.filter((c) => (c instanceof JiraProjectConnection && c.interestedInVersion(version))) as JiraProjectConnection[];
|
||||||
|
}
|
||||||
|
|
||||||
public getConnectionsForGenericWebhook(hookId: string): GenericHookConnection[] {
|
public getConnectionsForGenericWebhook(hookId: string): GenericHookConnection[] {
|
||||||
return this.connections.filter((c) => (c instanceof GenericHookConnection && c.hookId === hookId)) as GenericHookConnection[];
|
return this.connections.filter((c) => (c instanceof GenericHookConnection && c.hookId === hookId)) as GenericHookConnection[];
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { Connection, IConnection, IConnectionState, InstantiateConnectionOpts, ProvisionConnectionOpts } from "./IConnection";
|
import { Connection, IConnection, IConnectionState, InstantiateConnectionOpts, ProvisionConnectionOpts } from "./IConnection";
|
||||||
import { Appservice, StateEvent } from "matrix-bot-sdk";
|
import { Appservice, StateEvent } from "matrix-bot-sdk";
|
||||||
import { Logger } from "matrix-appservice-bridge";
|
import { Logger } from "matrix-appservice-bridge";
|
||||||
import { JiraIssueEvent, JiraIssueUpdatedEvent } from "../Jira/WebhookTypes";
|
import { JiraIssueEvent, JiraIssueUpdatedEvent, JiraVersionEvent } from "../Jira/WebhookTypes";
|
||||||
import { FormatUtil } from "../FormatUtil";
|
import { FormatUtil } from "../FormatUtil";
|
||||||
import markdownit from "markdown-it";
|
import markdownit from "markdown-it";
|
||||||
import { generateJiraWebLinkFromIssue } from "../Jira";
|
import { generateJiraWebLinkFromIssue, generateJiraWebLinkFromVersion } from "../Jira";
|
||||||
import { JiraProject } from "../Jira/Types";
|
import { JiraProject, JiraVersion } from "../Jira/Types";
|
||||||
import { botCommand, BotCommands, compileBotCommands } from "../BotCommands";
|
import { botCommand, BotCommands, compileBotCommands } from "../BotCommands";
|
||||||
import { MatrixMessageContent } from "../MatrixEvent";
|
import { MatrixMessageContent } from "../MatrixEvent";
|
||||||
import { CommandConnection } from "./CommandConnection";
|
import { CommandConnection } from "./CommandConnection";
|
||||||
@ -19,15 +19,21 @@ import { HookshotJiraApi } from "../Jira/Client";
|
|||||||
|
|
||||||
type JiraAllowedEventsNames =
|
type JiraAllowedEventsNames =
|
||||||
"issue_created" |
|
"issue_created" |
|
||||||
"issue_updated";
|
"issue_updated" |
|
||||||
|
"version_created" |
|
||||||
|
"version_updated" |
|
||||||
|
"version_released";
|
||||||
|
|
||||||
const JiraAllowedEvents: JiraAllowedEventsNames[] = [
|
const JiraAllowedEvents: JiraAllowedEventsNames[] = [
|
||||||
"issue_created" ,
|
"issue_created" ,
|
||||||
"issue_updated" ,
|
"issue_updated" ,
|
||||||
|
"version_created" ,
|
||||||
|
"version_updated" ,
|
||||||
|
"version_released",
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface JiraProjectConnectionState extends IConnectionState {
|
export interface JiraProjectConnectionState extends IConnectionState {
|
||||||
// legacy field, prefer url
|
// prefer url, but some events identify projects by id
|
||||||
id?: string;
|
id?: string;
|
||||||
url: string;
|
url: string;
|
||||||
events?: JiraAllowedEventsNames[],
|
events?: JiraAllowedEventsNames[],
|
||||||
@ -55,7 +61,10 @@ export type JiraProjectResponseItem = GetConnectionsResponseItem<JiraProjectConn
|
|||||||
|
|
||||||
|
|
||||||
function validateJiraConnectionState(state: unknown): JiraProjectConnectionState {
|
function validateJiraConnectionState(state: unknown): JiraProjectConnectionState {
|
||||||
const {url, commandPrefix, priority} = state as Partial<JiraProjectConnectionState>;
|
const {id, url, commandPrefix, priority} = state as Partial<JiraProjectConnectionState>;
|
||||||
|
if (id !== undefined && typeof id !== "string") {
|
||||||
|
throw new ApiError("Expected 'id' to be a string", ErrCode.BadValue);
|
||||||
|
}
|
||||||
if (url === undefined) {
|
if (url === undefined) {
|
||||||
throw new ApiError("Expected a 'url' property", ErrCode.BadValue);
|
throw new ApiError("Expected a 'url' property", ErrCode.BadValue);
|
||||||
}
|
}
|
||||||
@ -73,7 +82,7 @@ function validateJiraConnectionState(state: unknown): JiraProjectConnectionState
|
|||||||
} else if (events.find((ev) => !JiraAllowedEvents.includes(ev))?.length) {
|
} else if (events.find((ev) => !JiraAllowedEvents.includes(ev))?.length) {
|
||||||
throw new ApiError(`'events' can only contain ${JiraAllowedEvents.join(", ")}`, ErrCode.BadValue);
|
throw new ApiError(`'events' can only contain ${JiraAllowedEvents.join(", ")}`, ErrCode.BadValue);
|
||||||
}
|
}
|
||||||
return {url, commandPrefix, events, priority};
|
return {id, url, commandPrefix, events, priority};
|
||||||
}
|
}
|
||||||
|
|
||||||
const log = new Logger("JiraProjectConnection");
|
const log = new Logger("JiraProjectConnection");
|
||||||
@ -122,8 +131,13 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
throw Error('Expected projectKey to be defined');
|
throw Error('Expected projectKey to be defined');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Just need to check that the user can access this.
|
// Need to check that the user can access this.
|
||||||
await jiraResourceClient.getProject(connection.projectKey);
|
const project = await jiraResourceClient.getProject(connection.projectKey);
|
||||||
|
// Fetch the project's id now, to support events that identify projects by id instead of url
|
||||||
|
if (connection.state.id !== undefined && connection.state.id !== project.id) {
|
||||||
|
log.warn(`Updating ID of project ${connection.projectKey} from ${connection.state.id} to ${project.id}`);
|
||||||
|
connection.state.id = project.id;
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
throw new ApiError("Requested project was not found", ErrCode.ForbiddenUser);
|
throw new ApiError("Requested project was not found", ErrCode.ForbiddenUser);
|
||||||
}
|
}
|
||||||
@ -149,7 +163,11 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get projectKey() {
|
public get projectKey() {
|
||||||
const parts = this.projectUrl?.pathname.split('/');
|
return this.projectUrl ? JiraProjectConnection.getProjectKeyForUrl(this.projectUrl) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getProjectKeyForUrl(projectUrl: URL) {
|
||||||
|
const parts = projectUrl?.pathname.split('/');
|
||||||
return parts ? parts[parts.length - 1]?.toUpperCase() : undefined;
|
return parts ? parts[parts.length - 1]?.toUpperCase() : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +176,7 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
}
|
}
|
||||||
|
|
||||||
public toString() {
|
public toString() {
|
||||||
return `JiraProjectConnection ${this.projectId || this.projectUrl}`;
|
return `JiraProjectConnection ${this.projectUrl || this.projectId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isInterestedInHookEvent(eventName: JiraAllowedEventsNames, interestedByDefault = false) {
|
public isInterestedInHookEvent(eventName: JiraAllowedEventsNames, interestedByDefault = false) {
|
||||||
@ -176,6 +194,10 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interestedInVersion(version: JiraVersion) {
|
||||||
|
return this.projectId === version.projectId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URL of the project
|
* The URL of the project
|
||||||
* @example https://test.atlassian.net/jira/software/c/projects/PLAY
|
* @example https://test.atlassian.net/jira/software/c/projects/PLAY
|
||||||
@ -223,7 +245,7 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
if (!this.isInterestedInHookEvent('issue_created', true)) {
|
if (!this.isInterestedInHookEvent('issue_created', true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.info(`onIssueCreated ${this.roomId} ${this.projectId} ${data.issue.id}`);
|
log.info(`onIssueCreated ${this.roomId} ${this.projectUrl || this.projectId} ${data.issue.id}`);
|
||||||
|
|
||||||
const creator = data.issue.fields.creator;
|
const creator = data.issue.fields.creator;
|
||||||
if (!creator) {
|
if (!creator) {
|
||||||
@ -299,6 +321,7 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
for await (const project of resClient.getAllProjects()) {
|
for await (const project of resClient.getAllProjects()) {
|
||||||
allProjects.push({
|
allProjects.push({
|
||||||
state: {
|
state: {
|
||||||
|
id: project.id,
|
||||||
// Technically not the real URL, but good enough for hookshot!
|
// Technically not the real URL, but good enough for hookshot!
|
||||||
url: `${resClient.resource.url}/projects/${project.key}`,
|
url: `${resClient.resource.url}/projects/${project.key}`,
|
||||||
},
|
},
|
||||||
@ -317,7 +340,7 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
if (!this.isInterestedInHookEvent('issue_updated')) {
|
if (!this.isInterestedInHookEvent('issue_updated')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.info(`onJiraIssueUpdated ${this.roomId} ${this.projectId} ${data.issue.id}`);
|
log.info(`onJiraIssueUpdated ${this.roomId} ${this.projectUrl || this.projectId} ${data.issue.id}`);
|
||||||
const url = generateJiraWebLinkFromIssue(data.issue);
|
const url = generateJiraWebLinkFromIssue(data.issue);
|
||||||
let content = `${data.user.displayName} updated JIRA [${data.issue.key}](${url}): `;
|
let content = `${data.user.displayName} updated JIRA [${data.issue.key}](${url}): `;
|
||||||
|
|
||||||
@ -342,6 +365,29 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async onJiraVersionEvent(data: JiraVersionEvent) {
|
||||||
|
if (!this.isInterestedInHookEvent(data.webhookEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info(`onJiraVersionEvent ${this.roomId} ${this.projectUrl || this.projectId} ${data.webhookEvent}`);
|
||||||
|
const url = generateJiraWebLinkFromVersion({
|
||||||
|
...data.version,
|
||||||
|
projectId: data.version.projectId.toString(),
|
||||||
|
});
|
||||||
|
const action = data.webhookEvent.substring("version_".length);
|
||||||
|
const content =
|
||||||
|
`Version **${action}**` +
|
||||||
|
(this.projectKey && this.projectUrl ? ` for project [${this.projectKey}](${this.projectUrl})` : "") +
|
||||||
|
`: [${data.version.name}](${url}) (_${data.version.description}_)`;
|
||||||
|
|
||||||
|
await this.as.botIntent.sendEvent(this.roomId, {
|
||||||
|
msgtype: "m.notice",
|
||||||
|
body: content,
|
||||||
|
formatted_body: md.renderInline(content),
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async getUserClientForProject(userId: string) {
|
private async getUserClientForProject(userId: string) {
|
||||||
if (!this.projectUrl) {
|
if (!this.projectUrl) {
|
||||||
throw new CommandError("No-resource-origin", "Room is configured with an ID and not a URL, cannot determine correct JIRA client");
|
throw new CommandError("No-resource-origin", "Room is configured with an ID and not a URL, cannot determine correct JIRA client");
|
||||||
@ -467,9 +513,31 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
|
|
||||||
public async provisionerUpdateConfig(userId: string, config: Record<string, unknown>) {
|
public async provisionerUpdateConfig(userId: string, config: Record<string, unknown>) {
|
||||||
const validatedConfig = validateJiraConnectionState(config);
|
const validatedConfig = validateJiraConnectionState(config);
|
||||||
|
if (!validatedConfig.id) {
|
||||||
|
await this.updateProjectId(validatedConfig, userId);
|
||||||
|
}
|
||||||
await this.as.botClient.sendStateEvent(this.roomId, JiraProjectConnection.CanonicalEventType, this.stateKey, validatedConfig);
|
await this.as.botClient.sendStateEvent(this.roomId, JiraProjectConnection.CanonicalEventType, this.stateKey, validatedConfig);
|
||||||
this.state = validatedConfig;
|
this.state = validatedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateProjectId(validatedConfig: JiraProjectConnectionState, userIdForAuth: string) {
|
||||||
|
const jiraClient = await this.tokenStore.getJiraForUser(userIdForAuth);
|
||||||
|
if (!jiraClient) {
|
||||||
|
log.warn(`Cannot update JIRA project ID via user ${userIdForAuth} who is not authenticted with JIRA`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = new URL(validatedConfig.url);
|
||||||
|
const jiraResourceClient = await jiraClient.getClientForUrl(url);
|
||||||
|
if (!jiraResourceClient) {
|
||||||
|
log.warn(`Cannot update JIRA project ID via user ${userIdForAuth} who is not authenticated with this JIRA instance`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const projectKey = JiraProjectConnection.getProjectKeyForUrl(url);
|
||||||
|
if (projectKey) {
|
||||||
|
const project = await jiraResourceClient.getProject(projectKey);
|
||||||
|
validatedConfig.id = project.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -118,6 +118,7 @@ interface JiraAccountStatus {
|
|||||||
interface JiraProjectsListing {
|
interface JiraProjectsListing {
|
||||||
name: string;
|
name: string;
|
||||||
key: string;
|
key: string;
|
||||||
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,12 +184,13 @@ export class JiraProvisionerRouter {
|
|||||||
return next( new ApiError("Instance not known or not accessible to this user.", ErrCode.ForbiddenUser));
|
return next( new ApiError("Instance not known or not accessible to this user.", ErrCode.ForbiddenUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
const projects = [];
|
const projects: JiraProjectsListing[] = [];
|
||||||
try {
|
try {
|
||||||
for await (const project of resClient.getAllProjects()) {
|
for await (const project of resClient.getAllProjects()) {
|
||||||
projects.push({
|
projects.push({
|
||||||
key: project.key,
|
key: project.key,
|
||||||
name: project.name,
|
name: project.name,
|
||||||
|
id: project.id,
|
||||||
// Technically not the real URL, but good enough for hookshot!
|
// Technically not the real URL, but good enough for hookshot!
|
||||||
url: `${resClient.resource.url}/projects/${project.key}`,
|
url: `${resClient.resource.url}/projects/${project.key}`,
|
||||||
});
|
});
|
||||||
|
@ -68,6 +68,25 @@ export interface JiraIssue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface JiraVersion {
|
||||||
|
/**
|
||||||
|
* URL
|
||||||
|
*/
|
||||||
|
self: string;
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
name: string;
|
||||||
|
archived: boolean;
|
||||||
|
released: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
releaseDate?: string;
|
||||||
|
overdue: boolean;
|
||||||
|
userStartDate?: string;
|
||||||
|
userReleaseDate?: string;
|
||||||
|
project?: string;
|
||||||
|
projectId: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface JiraStoredToken {
|
export interface JiraStoredToken {
|
||||||
expires_in?: number;
|
expires_in?: number;
|
||||||
access_token: string;
|
access_token: string;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { JiraAccount, JiraComment, JiraIssue } from "./Types";
|
import { JiraAccount, JiraComment, JiraIssue, JiraVersion } from "./Types";
|
||||||
|
|
||||||
export interface IJiraWebhookEvent {
|
export interface IJiraWebhookEvent {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
@ -32,4 +32,9 @@ export interface JiraIssueUpdatedEvent extends JiraIssueEvent {
|
|||||||
toString: null;
|
toString: null;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JiraVersionEvent extends IJiraWebhookEvent {
|
||||||
|
webhookEvent: "version_created"|"version_updated"|"version_released";
|
||||||
|
version: JiraVersion;
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
import * as libRs from "../libRs";
|
import * as libRs from "../libRs";
|
||||||
|
|
||||||
export const generateJiraWebLinkFromIssue = libRs.generateJiraWeblinkFromIssue;
|
export const generateJiraWebLinkFromIssue = libRs.generateJiraWeblinkFromIssue;
|
||||||
|
export const generateJiraWebLinkFromVersion = libRs.generateJiraWeblinkFromVersion;
|
||||||
|
@ -52,3 +52,14 @@ pub struct JiraIssueMessageBody {
|
|||||||
#[napi(js_name = "external_url")]
|
#[napi(js_name = "external_url")]
|
||||||
pub external_url: String,
|
pub external_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct JiraVersion {
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
pub _self: String,
|
||||||
|
pub id: String,
|
||||||
|
pub description: String,
|
||||||
|
pub name: String,
|
||||||
|
pub projectId: String,
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::types::JiraIssueLight;
|
use super::types::{JiraIssueLight, JiraVersion};
|
||||||
use napi::bindgen_prelude::*;
|
use napi::bindgen_prelude::*;
|
||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@ -23,3 +23,25 @@ pub fn generate_jira_web_link_from_issue(jira_issue: &JiraIssueLight) -> Result<
|
|||||||
Err(err) => Err(Error::new(Status::Unknown, err.to_string())),
|
Err(err) => Err(Error::new(Status::Unknown, err.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a URL for a given Jira Version object.
|
||||||
|
#[napi(js_name = "generateJiraWeblinkFromVersion")]
|
||||||
|
pub fn js_generate_jira_web_link_from_version(jira_version: JiraVersion) -> Result<String> {
|
||||||
|
return generate_jira_web_link_from_version(&jira_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_jira_web_link_from_version(jira_version: &JiraVersion) -> Result<String> {
|
||||||
|
let result = Url::parse(&jira_version._self);
|
||||||
|
match result {
|
||||||
|
Ok(url) => Ok(format!(
|
||||||
|
"{}://{}{}/projects/{}/versions/{}",
|
||||||
|
url.scheme(),
|
||||||
|
url.host_str().unwrap(),
|
||||||
|
url.port()
|
||||||
|
.map_or(String::new(), |port| format!(":{}", port)),
|
||||||
|
jira_version.projectId,
|
||||||
|
jira_version.id
|
||||||
|
)),
|
||||||
|
Err(err) => Err(Error::new(Status::Unknown, err.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -176,6 +176,12 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
|||||||
<EventCheckbox allowedEvents={allowedEvents} eventName="issue_created" onChange={toggleEvent}>Created</EventCheckbox>
|
<EventCheckbox allowedEvents={allowedEvents} eventName="issue_created" onChange={toggleEvent}>Created</EventCheckbox>
|
||||||
<EventCheckbox allowedEvents={allowedEvents} eventName="issue_updated" onChange={toggleEvent}>Updated</EventCheckbox>
|
<EventCheckbox allowedEvents={allowedEvents} eventName="issue_updated" onChange={toggleEvent}>Updated</EventCheckbox>
|
||||||
</ul>
|
</ul>
|
||||||
|
Versions
|
||||||
|
<ul>
|
||||||
|
<EventCheckbox allowedEvents={allowedEvents} eventName="version_created" onChange={toggleEvent}>Created</EventCheckbox>
|
||||||
|
<EventCheckbox allowedEvents={allowedEvents} eventName="version_updated" onChange={toggleEvent}>Updated</EventCheckbox>
|
||||||
|
<EventCheckbox allowedEvents={allowedEvents} eventName="version_released" onChange={toggleEvent}>Released</EventCheckbox>
|
||||||
|
</ul>
|
||||||
</ul>
|
</ul>
|
||||||
</InputField>
|
</InputField>
|
||||||
<ButtonSet>
|
<ButtonSet>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user