Skip to content

Commit a75228b

Browse files
src: add and delete file webhooks when designs are attached or removed (#260)
* Add file webhook repository and types * install figma rest api spec * Create file webhooks for a design when it's linked * delete file webhooks on disassociate or app uninstall * update tests * fix integration test request * don't throw errors if creating file webhook fails from user perms * add tests for disassociated use case * simplify FigmaFileWebhook class into a type * split webhook creation into two to avoid dangling webhooks
1 parent 3f210f4 commit a75228b

22 files changed

Lines changed: 1069 additions & 71 deletions

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"test:unit": "DOTENV_CONFIG_PATH=.env.test jest -c jest.config.unit.ts"
3434
},
3535
"dependencies": {
36+
"@figma/rest-api-spec": "^0.33.0",
3637
"@prisma/client": "^5.1.1",
3738
"ajv": "^8.12.0",
3839
"atlassian-jwt": "^2.0.2",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export enum FigmaFileWebhookEventType {
2+
FILE_UPDATE = 'FILE_UPDATE',
3+
DEV_MODE_STATUS_UPDATE = 'DEV_MODE_STATUS_UPDATE',
4+
}
5+
6+
export type FigmaFileWebhook = {
7+
readonly id: string;
8+
readonly webhookId: string;
9+
readonly webhookPasscode: string;
10+
readonly fileKey: string;
11+
readonly eventType: FigmaFileWebhookEventType;
12+
readonly createdBy: {
13+
readonly atlassianUserId: string;
14+
readonly connectInstallationId: string;
15+
};
16+
};
17+
18+
export type FigmaFileWebhookCreateParams = {
19+
readonly webhookId: string;
20+
readonly webhookPasscode: string;
21+
readonly fileKey: string;
22+
readonly eventType: FigmaFileWebhookEventType;
23+
readonly createdBy: {
24+
readonly atlassianUserId: string;
25+
readonly connectInstallationId: string;
26+
};
27+
};

src/domain/entities/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from './figma-oauth2-user-credentials';
77
export * from './figma-team';
88
export * from './figma-user';
99
export * from './jira-issue';
10+
export * from './figma-file-webhook';

src/domain/entities/testing/mocks.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
ConnectInstallation,
88
ConnectInstallationCreateParams,
99
ConnectUserInfo,
10+
FigmaFileWebhook,
1011
FigmaOAuth2UserCredentialsCreateParams,
1112
FigmaTeamCreateParams,
1213
FigmaTeamSummary,
@@ -16,6 +17,7 @@ import {
1617
AtlassianDesignStatus,
1718
AtlassianDesignType,
1819
FigmaDesignIdentifier,
20+
FigmaFileWebhookEventType,
1921
FigmaOAuth2UserCredentials,
2022
FigmaTeam,
2123
FigmaTeamAuthStatus,
@@ -271,3 +273,24 @@ export const generateFigmaTeamSummary = ({
271273
teamName,
272274
authStatus: status,
273275
});
276+
277+
export const generateFigmaFileWebhook = ({
278+
id = generateNumericStringId(),
279+
webhookId = uuidv4(),
280+
webhookPasscode = uuidv4(),
281+
fileKey = generateFigmaFileKey(),
282+
eventType = FigmaFileWebhookEventType.FILE_UPDATE,
283+
createdBy = {
284+
atlassianUserId: uuidv4(),
285+
connectInstallationId: generateNumericStringId(),
286+
},
287+
}: Partial<FigmaFileWebhook> = {}): FigmaFileWebhook => {
288+
return {
289+
id,
290+
webhookId,
291+
webhookPasscode,
292+
fileKey,
293+
eventType,
294+
createdBy,
295+
};
296+
};

src/infrastructure/figma/figma-client/figma-client.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import type {
2+
PostWebhookRequestBody,
3+
PostWebhookResponse,
4+
} from '@figma/rest-api-spec';
15
import axios from 'axios';
26
import { withParser } from 'stream-json/filters/Filter';
37
import { streamValues } from 'stream-json/streamers/StreamValues';
@@ -6,20 +10,18 @@ import type { Stream } from 'stream';
610

711
import {
812
CREATE_DEV_RESOURCE_RESPONSE_SCHEMA,
9-
CREATE_WEBHOOK_RESPONSE,
1013
GET_DEV_RESOURCE_RESPONSE_SCHEMA,
1114
GET_FILE_META_RESPONSE_SCHEMA,
1215
GET_FILE_RESPONSE_SCHEMA,
1316
GET_ME_RESPONSE_SCHEMA,
1417
GET_OAUTH2_TOKEN_RESPONSE_SCHEMA,
1518
GET_TEAM_PROJECTS_RESPONSE_SCHEMA,
19+
POST_WEBHOOK_RESPONSE_SCHEMA,
1620
REFRESH_OAUTH2_TOKEN_RESPONSE_SCHEMA,
1721
} from './schemas';
1822
import type {
1923
CreateDevResourcesRequest,
2024
CreateDevResourcesResponse,
21-
CreateWebhookRequest,
22-
CreateWebhookResponse,
2325
DeleteDevResourceRequest,
2426
GetDevResourcesRequest,
2527
GetDevResourcesResponse,
@@ -377,9 +379,9 @@ export class FigmaClient {
377379
* @throws {HttpClientError} An error associated with specific HTTP response status codes.
378380
*/
379381
createWebhook = async (
380-
request: CreateWebhookRequest,
382+
request: PostWebhookRequestBody,
381383
accessToken: string,
382-
): Promise<CreateWebhookResponse> =>
384+
): Promise<PostWebhookResponse> =>
383385
withAxiosErrorTranslation(async () => {
384386
const url = new URL(`v2/webhooks`, getConfig().figma.apiBaseUrl);
385387

@@ -389,7 +391,7 @@ export class FigmaClient {
389391
},
390392
});
391393

392-
assertSchema(response.data, CREATE_WEBHOOK_RESPONSE);
394+
assertSchema(response.data, POST_WEBHOOK_RESPONSE_SCHEMA);
393395

394396
return response.data;
395397
});

src/infrastructure/figma/figma-client/schemas.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
* Contains JSON schema definitions for Figma API responses. Used for validating the types of incoming data on the entry
33
* points to the application.
44
*/
5+
import type { PostWebhookResponse } from '@figma/rest-api-spec';
6+
57
import type {
68
CreateDevResourcesResponse,
7-
CreateWebhookResponse,
89
DevResource,
910
ErrorResponse,
1011
GetDevResourcesResponse,
@@ -166,30 +167,34 @@ export const GET_DEV_RESOURCE_RESPONSE_SCHEMA: JSONSchemaTypeWithId<GetDevResour
166167
required: ['dev_resources'],
167168
};
168169

169-
export const CREATE_WEBHOOK_RESPONSE: JSONSchemaTypeWithId<CreateWebhookResponse> =
170+
export const POST_WEBHOOK_RESPONSE_SCHEMA: JSONSchemaTypeWithId<PostWebhookResponse> =
170171
{
171172
$id: 'figma-api:post:v2/webhooks:response',
172173
type: 'object',
173174
properties: {
174175
id: { type: 'string' },
175176
team_id: { type: 'string' },
176-
event_type: { type: 'string' },
177+
event_type: { type: 'string' }, // This should match WebhookV2Event type
178+
context: { type: 'string' },
179+
context_id: { type: 'string' },
180+
plan_api_id: { type: 'string' },
177181
client_id: { type: 'string' },
178182
endpoint: { type: 'string' },
179183
passcode: { type: 'string' },
180184
status: { type: 'string' },
181185
description: { type: 'string' },
182-
protocol_version: { type: 'string' },
183186
},
184187
required: [
185188
'id',
186189
'team_id',
190+
'context',
191+
'context_id',
192+
'plan_api_id',
187193
'event_type',
188194
'client_id',
189195
'endpoint',
190196
'passcode',
191197
'status',
192-
'protocol_version',
193198
],
194199
};
195200

src/infrastructure/figma/figma-client/testing/mocks.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import type {
2+
PostWebhookResponse,
3+
WebhookV2Event,
4+
WebhookV2Status,
5+
} from '@figma/rest-api-spec';
16
import { v4 as uuidv4 } from 'uuid';
27

38
import type {
49
CreateDevResourceError,
510
CreateDevResourcesRequest,
611
CreateDevResourcesResponse,
7-
CreateWebhookResponse,
812
GetDevResourcesResponse,
913
GetFileMetaResponse,
1014
GetFileResponse,
@@ -215,23 +219,26 @@ export const generateEmptyDevResourcesResponse =
215219
export const generateCreateWebhookResponse = ({
216220
id = uuidv4(),
217221
teamId = uuidv4(),
218-
eventType = 'FILE_UPDATE',
222+
planApiId = `organization:${uuidv4()}`,
223+
context = 'team',
224+
eventType = 'FILE_UPDATE' as WebhookV2Event,
219225
clientId = uuidv4(),
220226
endpoint = `https://figma-for-jira.atlassian.com/figma/webhooks`,
221227
passcode = uuidv4(),
222-
status = 'ACTIVE',
228+
status = 'ACTIVE' as WebhookV2Status,
223229
description = 'Figma for Jira',
224-
protocolVersion = '2',
225-
} = {}): CreateWebhookResponse => ({
230+
} = {}): PostWebhookResponse => ({
226231
id: id,
227232
team_id: teamId,
233+
context: context,
234+
context_id: teamId,
228235
event_type: eventType,
229236
client_id: clientId,
230237
endpoint,
231238
passcode,
232239
status,
233240
description,
234-
protocol_version: protocolVersion,
241+
plan_api_id: planApiId,
235242
});
236243

237244
export const generateGetTeamProjectsResponse = ({

src/infrastructure/figma/figma-client/types.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -102,27 +102,6 @@ export type DeleteDevResourceRequest = {
102102
readonly accessToken: string;
103103
};
104104

105-
export type CreateWebhookRequest = {
106-
readonly event_type: string;
107-
readonly team_id: string;
108-
readonly endpoint: string;
109-
readonly passcode: string;
110-
readonly status?: string;
111-
readonly description?: string;
112-
};
113-
114-
export type CreateWebhookResponse = {
115-
readonly id: string;
116-
readonly team_id: string;
117-
readonly event_type: string;
118-
readonly client_id: string;
119-
readonly endpoint: string;
120-
readonly passcode: string;
121-
readonly status: string;
122-
readonly description: string | null;
123-
readonly protocol_version: string;
124-
};
125-
126105
export type GetTeamProjectsResponse = {
127106
readonly name: string;
128107
readonly projects: {

0 commit comments

Comments
 (0)