feat: outgoing webhooks
Owners and admins can point a project at other systems: a signed JSON POST (HMAC-SHA256 of the body) when tickets, comments, releases or sprints change, filtered by event or family. Deliveries are retried a couple of times, the last one is shown, and a ping can be sent from the settings. Private addresses are refused unless WEBHOOKS_ALLOW_PRIVATE is set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import {
|
||||
Webhook,
|
||||
WebhookDelivery,
|
||||
WebhooksGateway,
|
||||
} from '@task-stream/web/tracker/domain';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import {
|
||||
CreateWebhookDocument,
|
||||
DeleteWebhookDocument,
|
||||
TestWebhookDocument,
|
||||
UpdateWebhookDocument,
|
||||
WebhookEventsDocument,
|
||||
WebhooksDocument,
|
||||
} from '../graphql/generated';
|
||||
import { GraphqlOperations } from './graphql-operations';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlWebhooksGateway extends WebhooksGateway {
|
||||
private readonly gql = inject(GraphqlOperations);
|
||||
|
||||
list(projectId: string): Observable<Webhook[]> {
|
||||
return this.gql
|
||||
.query(WebhooksDocument, { projectId })
|
||||
.pipe(map((d) => d.webhooks as Webhook[]));
|
||||
}
|
||||
|
||||
events(): Observable<string[]> {
|
||||
return this.gql
|
||||
.query(WebhookEventsDocument, {})
|
||||
.pipe(map((d) => d.webhookEvents));
|
||||
}
|
||||
|
||||
create(
|
||||
projectId: string,
|
||||
url: string,
|
||||
events: string[],
|
||||
): Observable<{ id: string; secret: string }> {
|
||||
return this.gql
|
||||
.mutate(CreateWebhookDocument, { input: { projectId, url, events } })
|
||||
.pipe(map((d) => d.createWebhook));
|
||||
}
|
||||
|
||||
update(
|
||||
webhookId: string,
|
||||
changes: { url?: string; events?: string[]; active?: boolean },
|
||||
): Observable<Webhook> {
|
||||
return this.gql
|
||||
.mutate(UpdateWebhookDocument, { input: { webhookId, ...changes } })
|
||||
.pipe(map((d) => d.updateWebhook as Webhook));
|
||||
}
|
||||
|
||||
remove(webhookId: string): Observable<void> {
|
||||
return this.gql
|
||||
.mutate(DeleteWebhookDocument, { webhookId })
|
||||
.pipe(map(() => undefined));
|
||||
}
|
||||
|
||||
test(webhookId: string): Observable<WebhookDelivery> {
|
||||
return this.gql
|
||||
.mutate(TestWebhookDocument, { webhookId })
|
||||
.pipe(map((d) => d.testWebhook));
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,13 @@ export type CreateSprintInput = {
|
||||
startDate: string;
|
||||
};
|
||||
|
||||
export type CreateWebhookInput = {
|
||||
/** Event names, family.* or * */
|
||||
events: Array<string>;
|
||||
projectId: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type DevLinkKind = 'BRANCH' | 'COMMIT' | 'PULL_REQUEST';
|
||||
|
||||
export type DevLinkState = 'CLOSED' | 'MERGED' | 'OPEN';
|
||||
@@ -240,6 +247,13 @@ export type UpdateSprintInput = {
|
||||
startDate?: string | null | undefined;
|
||||
};
|
||||
|
||||
export type UpdateWebhookInput = {
|
||||
active?: boolean | null | undefined;
|
||||
events?: Array<string> | null | undefined;
|
||||
url?: string | null | undefined;
|
||||
webhookId: string;
|
||||
};
|
||||
|
||||
export type WorkflowStatusInput = {
|
||||
archived?: boolean;
|
||||
category: StatusCategory;
|
||||
@@ -1656,6 +1670,106 @@ export type MyProjectsChangedSubscription = {
|
||||
};
|
||||
};
|
||||
|
||||
export type WebhookFieldsFragment = {
|
||||
id: string;
|
||||
url: string;
|
||||
events: Array<string>;
|
||||
active: boolean;
|
||||
createdAt: string;
|
||||
createdBy: { id: string; name: string };
|
||||
lastDelivery: {
|
||||
at: string;
|
||||
event: string;
|
||||
status: number | null;
|
||||
ok: boolean;
|
||||
error: string | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type WebhookDeliveryFieldsFragment = {
|
||||
at: string;
|
||||
event: string;
|
||||
status: number | null;
|
||||
ok: boolean;
|
||||
error: string | null;
|
||||
};
|
||||
|
||||
export type WebhooksQueryVariables = Exact<{
|
||||
projectId: string;
|
||||
}>;
|
||||
|
||||
export type WebhooksQuery = {
|
||||
webhooks: Array<{
|
||||
id: string;
|
||||
url: string;
|
||||
events: Array<string>;
|
||||
active: boolean;
|
||||
createdAt: string;
|
||||
createdBy: { id: string; name: string };
|
||||
lastDelivery: {
|
||||
at: string;
|
||||
event: string;
|
||||
status: number | null;
|
||||
ok: boolean;
|
||||
error: string | null;
|
||||
} | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type WebhookEventsQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type WebhookEventsQuery = { webhookEvents: Array<string> };
|
||||
|
||||
export type CreateWebhookMutationVariables = Exact<{
|
||||
input: CreateWebhookInput;
|
||||
}>;
|
||||
|
||||
export type CreateWebhookMutation = {
|
||||
createWebhook: { id: string; secret: string };
|
||||
};
|
||||
|
||||
export type UpdateWebhookMutationVariables = Exact<{
|
||||
input: UpdateWebhookInput;
|
||||
}>;
|
||||
|
||||
export type UpdateWebhookMutation = {
|
||||
updateWebhook: {
|
||||
id: string;
|
||||
url: string;
|
||||
events: Array<string>;
|
||||
active: boolean;
|
||||
createdAt: string;
|
||||
createdBy: { id: string; name: string };
|
||||
lastDelivery: {
|
||||
at: string;
|
||||
event: string;
|
||||
status: number | null;
|
||||
ok: boolean;
|
||||
error: string | null;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type DeleteWebhookMutationVariables = Exact<{
|
||||
webhookId: string;
|
||||
}>;
|
||||
|
||||
export type DeleteWebhookMutation = { deleteWebhook: boolean };
|
||||
|
||||
export type TestWebhookMutationVariables = Exact<{
|
||||
webhookId: string;
|
||||
}>;
|
||||
|
||||
export type TestWebhookMutation = {
|
||||
testWebhook: {
|
||||
at: string;
|
||||
event: string;
|
||||
status: number | null;
|
||||
ok: boolean;
|
||||
error: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
export const UserRefFieldsFragmentDoc = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
@@ -2484,6 +2598,111 @@ export const SprintFieldsFragmentDoc = {
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<SprintFieldsFragment, unknown>;
|
||||
export const WebhookDeliveryFieldsFragmentDoc = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'WebhookDeliveryFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'WebhookDelivery' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'event' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'status' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'ok' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'error' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<WebhookDeliveryFieldsFragment, unknown>;
|
||||
export const WebhookFieldsFragmentDoc = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'WebhookFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'Webhook' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'url' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'events' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'active' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'createdBy' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'UserRefFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'lastDelivery' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'WebhookDeliveryFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'UserRefFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'UserRef' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'WebhookDeliveryFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'WebhookDelivery' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'event' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'status' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'ok' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'error' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<WebhookFieldsFragment, unknown>;
|
||||
export const GitIntegrationDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
@@ -9995,3 +10214,450 @@ export const MyProjectsChangedDocument = {
|
||||
MyProjectsChangedSubscription,
|
||||
MyProjectsChangedSubscriptionVariables
|
||||
>;
|
||||
export const WebhooksDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'query',
|
||||
name: { kind: 'Name', value: 'Webhooks' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'projectId' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'webhooks' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'projectId' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'projectId' },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'WebhookFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'UserRefFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'UserRef' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'WebhookDeliveryFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'WebhookDelivery' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'event' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'status' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'ok' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'error' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'WebhookFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'Webhook' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'url' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'events' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'active' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'createdBy' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'UserRefFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'lastDelivery' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'WebhookDeliveryFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<WebhooksQuery, WebhooksQueryVariables>;
|
||||
export const WebhookEventsDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'query',
|
||||
name: { kind: 'Name', value: 'WebhookEvents' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'webhookEvents' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<WebhookEventsQuery, WebhookEventsQueryVariables>;
|
||||
export const CreateWebhookDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'mutation',
|
||||
name: { kind: 'Name', value: 'CreateWebhook' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'CreateWebhookInput' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'createWebhook' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'secret' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
CreateWebhookMutation,
|
||||
CreateWebhookMutationVariables
|
||||
>;
|
||||
export const UpdateWebhookDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'mutation',
|
||||
name: { kind: 'Name', value: 'UpdateWebhook' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'UpdateWebhookInput' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'updateWebhook' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'WebhookFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'UserRefFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'UserRef' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'WebhookDeliveryFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'WebhookDelivery' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'event' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'status' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'ok' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'error' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'WebhookFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'Webhook' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'url' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'events' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'active' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'createdBy' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'UserRefFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'lastDelivery' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'WebhookDeliveryFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
UpdateWebhookMutation,
|
||||
UpdateWebhookMutationVariables
|
||||
>;
|
||||
export const DeleteWebhookDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'mutation',
|
||||
name: { kind: 'Name', value: 'DeleteWebhook' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'webhookId' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'deleteWebhook' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'webhookId' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'webhookId' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
DeleteWebhookMutation,
|
||||
DeleteWebhookMutationVariables
|
||||
>;
|
||||
export const TestWebhookDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'mutation',
|
||||
name: { kind: 'Name', value: 'TestWebhook' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'webhookId' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'testWebhook' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'webhookId' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'webhookId' },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'WebhookDeliveryFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'WebhookDeliveryFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'WebhookDelivery' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'event' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'status' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'ok' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'error' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<TestWebhookMutation, TestWebhookMutationVariables>;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
fragment WebhookFields on Webhook {
|
||||
id
|
||||
url
|
||||
events
|
||||
active
|
||||
createdAt
|
||||
createdBy {
|
||||
...UserRefFields
|
||||
}
|
||||
lastDelivery {
|
||||
...WebhookDeliveryFields
|
||||
}
|
||||
}
|
||||
|
||||
fragment WebhookDeliveryFields on WebhookDelivery {
|
||||
at
|
||||
event
|
||||
status
|
||||
ok
|
||||
error
|
||||
}
|
||||
|
||||
query Webhooks($projectId: ID!) {
|
||||
webhooks(projectId: $projectId) {
|
||||
...WebhookFields
|
||||
}
|
||||
}
|
||||
|
||||
query WebhookEvents {
|
||||
webhookEvents
|
||||
}
|
||||
|
||||
mutation CreateWebhook($input: CreateWebhookInput!) {
|
||||
createWebhook(input: $input) {
|
||||
id
|
||||
secret
|
||||
}
|
||||
}
|
||||
|
||||
mutation UpdateWebhook($input: UpdateWebhookInput!) {
|
||||
updateWebhook(input: $input) {
|
||||
...WebhookFields
|
||||
}
|
||||
}
|
||||
|
||||
mutation DeleteWebhook($webhookId: ID!) {
|
||||
deleteWebhook(webhookId: $webhookId)
|
||||
}
|
||||
|
||||
mutation TestWebhook($webhookId: ID!) {
|
||||
testWebhook(webhookId: $webhookId) {
|
||||
...WebhookDeliveryFields
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
SavedFiltersGateway,
|
||||
SprintsGateway,
|
||||
GitGateway,
|
||||
WebhooksGateway,
|
||||
TrackerRealtimeGateway,
|
||||
UsersGateway,
|
||||
} from '@task-stream/web/tracker/domain';
|
||||
@@ -30,6 +31,7 @@ import { GraphqlRealtimeGateway } from './adapters/graphql-realtime.gateway';
|
||||
import { GraphqlSavedFiltersGateway } from './adapters/graphql-saved-filters.gateway';
|
||||
import { GraphqlSprintsGateway } from './adapters/graphql-sprints.gateway';
|
||||
import { GraphqlGitGateway } from './adapters/graphql-git.gateway';
|
||||
import { GraphqlWebhooksGateway } from './adapters/graphql-webhooks.gateway';
|
||||
|
||||
/** Ports → GraphQL adapters (and REST for files). */
|
||||
export function provideTrackerInfrastructure(): EnvironmentProviders {
|
||||
@@ -46,5 +48,6 @@ export function provideTrackerInfrastructure(): EnvironmentProviders {
|
||||
{ provide: SavedFiltersGateway, useClass: GraphqlSavedFiltersGateway },
|
||||
{ provide: SprintsGateway, useClass: GraphqlSprintsGateway },
|
||||
{ provide: GitGateway, useClass: GraphqlGitGateway },
|
||||
{ provide: WebhooksGateway, useClass: GraphqlWebhooksGateway },
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user