@atlaskit/link-client-extension 8.0.0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/compass.yml +7 -4
- package/dist/cjs/services/use-integration-actions-client/build-request-headers.js +40 -0
- package/dist/cjs/services/use-integration-actions-client/constants.js +18 -0
- package/dist/cjs/services/use-integration-actions-client/get-agg-url.js +34 -0
- package/dist/cjs/services/use-integration-actions-client/index.js +143 -0
- package/dist/cjs/services/use-integration-actions-client/integration-action-error.js +42 -0
- package/dist/cjs/services/use-integration-actions-client/mocks.js +91 -0
- package/dist/cjs/services/use-integration-actions-client/parse-execute-integration-action-response.js +168 -0
- package/dist/cjs/services/use-integration-actions-client/types.js +1 -0
- package/dist/es2019/services/use-integration-actions-client/build-request-headers.js +36 -0
- package/dist/es2019/services/use-integration-actions-client/constants.js +29 -0
- package/dist/es2019/services/use-integration-actions-client/get-agg-url.js +28 -0
- package/dist/es2019/services/use-integration-actions-client/index.js +84 -0
- package/dist/es2019/services/use-integration-actions-client/integration-action-error.js +21 -0
- package/dist/es2019/services/use-integration-actions-client/mocks.js +85 -0
- package/dist/es2019/services/use-integration-actions-client/parse-execute-integration-action-response.js +148 -0
- package/dist/es2019/services/use-integration-actions-client/types.js +0 -0
- package/dist/esm/services/use-integration-actions-client/build-request-headers.js +35 -0
- package/dist/esm/services/use-integration-actions-client/constants.js +12 -0
- package/dist/esm/services/use-integration-actions-client/get-agg-url.js +28 -0
- package/dist/esm/services/use-integration-actions-client/index.js +136 -0
- package/dist/esm/services/use-integration-actions-client/integration-action-error.js +35 -0
- package/dist/esm/services/use-integration-actions-client/mocks.js +85 -0
- package/dist/esm/services/use-integration-actions-client/parse-execute-integration-action-response.js +161 -0
- package/dist/esm/services/use-integration-actions-client/types.js +0 -0
- package/dist/types/services/use-integration-actions-client/build-request-headers.d.ts +15 -0
- package/dist/types/services/use-integration-actions-client/constants.d.ts +9 -0
- package/dist/types/services/use-integration-actions-client/get-agg-url.d.ts +13 -0
- package/dist/types/services/use-integration-actions-client/index.d.ts +32 -0
- package/dist/types/services/use-integration-actions-client/integration-action-error.d.ts +26 -0
- package/dist/types/services/use-integration-actions-client/mocks.d.ts +11 -0
- package/dist/types/services/use-integration-actions-client/parse-execute-integration-action-response.d.ts +12 -0
- package/dist/types/services/use-integration-actions-client/types.d.ts +112 -0
- package/package.json +6 -6
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock AGG `actions { execute }` responses, based on what the ASIMO-5527 spike saw
|
|
3
|
+
* on staging.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export var mockExecuteIntegrationActionSuccessResponse = {
|
|
7
|
+
data: {
|
|
8
|
+
actions: {
|
|
9
|
+
execute: {
|
|
10
|
+
status: 200,
|
|
11
|
+
outputs: {
|
|
12
|
+
entities: {
|
|
13
|
+
comments: [{
|
|
14
|
+
id: 'comment-1',
|
|
15
|
+
content: 'Looks good to me',
|
|
16
|
+
author: {
|
|
17
|
+
displayName: 'Jane Smith'
|
|
18
|
+
},
|
|
19
|
+
createdTime: '2026-10-27T01:00:00.000Z'
|
|
20
|
+
}]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export var mockAuthUrl = 'https://id.atlassian.com/outboundAuth/start?key=google-drive';
|
|
28
|
+
|
|
29
|
+
/** IS returns consent errors in `outputs.errors` with HTTP 200 and no GraphQL `errors`. */
|
|
30
|
+
export var mockExecuteIntegrationActionConsentRequiredResponse = {
|
|
31
|
+
data: {
|
|
32
|
+
actions: {
|
|
33
|
+
execute: {
|
|
34
|
+
status: 401,
|
|
35
|
+
outputs: {
|
|
36
|
+
errors: [{
|
|
37
|
+
message: 'User consent is required to access this integration',
|
|
38
|
+
statusCode: 401,
|
|
39
|
+
extensions: {
|
|
40
|
+
errorType: 'USER_CONSENT_REQUIRED',
|
|
41
|
+
errorCategory: 'AUTH',
|
|
42
|
+
retryable: false,
|
|
43
|
+
authUrl: mockAuthUrl
|
|
44
|
+
}
|
|
45
|
+
}]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
export var mockExecuteIntegrationActionOperationFailureResponse = {
|
|
52
|
+
data: {
|
|
53
|
+
actions: {
|
|
54
|
+
execute: {
|
|
55
|
+
status: 404,
|
|
56
|
+
outputs: {
|
|
57
|
+
errors: [{
|
|
58
|
+
message: 'File not found',
|
|
59
|
+
statusCode: 404,
|
|
60
|
+
extensions: {
|
|
61
|
+
errorType: 'NOT_FOUND',
|
|
62
|
+
errorCategory: 'CLIENT',
|
|
63
|
+
retryable: false
|
|
64
|
+
}
|
|
65
|
+
}]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
export var mockExecuteIntegrationActionGraphQLErrorResponse = {
|
|
72
|
+
data: {
|
|
73
|
+
actions: {
|
|
74
|
+
execute: null
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
errors: [{
|
|
78
|
+
message: 'Action not found for filter',
|
|
79
|
+
path: ['actions', 'execute'],
|
|
80
|
+
extensions: {
|
|
81
|
+
classification: 'DataFetchingException',
|
|
82
|
+
statusCode: 400
|
|
83
|
+
}
|
|
84
|
+
}]
|
|
85
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import _typeof from "@babel/runtime/helpers/typeof";
|
|
3
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
4
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
5
|
+
import { GENERIC_GRAPHQL_ERROR_MESSAGE, GENERIC_OPERATION_ERROR_MESSAGE, USER_CONSENT_REQUIRED } from './constants';
|
|
6
|
+
import { IntegrationActionError } from './integration-action-error';
|
|
7
|
+
var isRecord = function isRecord(value) {
|
|
8
|
+
return _typeof(value) === 'object' && value !== null && !Array.isArray(value);
|
|
9
|
+
};
|
|
10
|
+
var isSuccessStatus = function isSuccessStatus(status) {
|
|
11
|
+
return status >= 200 && status < 300;
|
|
12
|
+
};
|
|
13
|
+
var getStringExtension = function getStringExtension(extensions, key) {
|
|
14
|
+
var value = extensions === null || extensions === void 0 ? void 0 : extensions[key];
|
|
15
|
+
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns `authUrl` from the extensions only if it's an absolute `https:` URL, so a malformed or
|
|
20
|
+
* unsafe value (e.g. `javascript:`) is never handed to the UI to open.
|
|
21
|
+
*/
|
|
22
|
+
var getAuthUrl = function getAuthUrl(extensions) {
|
|
23
|
+
var value = getStringExtension(extensions, 'authUrl');
|
|
24
|
+
if (!value) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return new URL(value).protocol === 'https:' ? value : undefined;
|
|
29
|
+
} catch (_unused) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Normalises an entry of `outputs.errors`, which IS doesn't guarantee is well-formed. */
|
|
35
|
+
var toOperationErrorEntry = function toOperationErrorEntry(error) {
|
|
36
|
+
if (typeof error === 'string') {
|
|
37
|
+
return {
|
|
38
|
+
message: error || GENERIC_OPERATION_ERROR_MESSAGE
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (!isRecord(error)) {
|
|
42
|
+
return {
|
|
43
|
+
message: GENERIC_OPERATION_ERROR_MESSAGE
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return _objectSpread(_objectSpread({
|
|
47
|
+
message: typeof error.message === 'string' && error.message !== '' ? error.message : GENERIC_OPERATION_ERROR_MESSAGE
|
|
48
|
+
}, isRecord(error.extensions) ? {
|
|
49
|
+
extensions: error.extensions
|
|
50
|
+
} : {}), typeof error.statusCode === 'number' ? {
|
|
51
|
+
statusCode: error.statusCode
|
|
52
|
+
} : {});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Normalises an entry of the GraphQL `errors` array, which may be malformed. */
|
|
56
|
+
var toGraphQLErrorEntry = function toGraphQLErrorEntry(error) {
|
|
57
|
+
if (!isRecord(error)) {
|
|
58
|
+
return {
|
|
59
|
+
message: typeof error === 'string' && error ? error : GENERIC_GRAPHQL_ERROR_MESSAGE
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return _objectSpread(_objectSpread({
|
|
63
|
+
message: typeof error.message === 'string' && error.message !== '' ? error.message : GENERIC_GRAPHQL_ERROR_MESSAGE
|
|
64
|
+
}, isRecord(error.extensions) ? {
|
|
65
|
+
extensions: error.extensions
|
|
66
|
+
} : {}), Array.isArray(error.path) ? {
|
|
67
|
+
path: error.path
|
|
68
|
+
} : {});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** A non-empty `outputs.errors` always means the action failed, even if the entries are malformed. */
|
|
72
|
+
var getOperationErrors = function getOperationErrors(outputs) {
|
|
73
|
+
var errors = isRecord(outputs) ? outputs.errors : undefined;
|
|
74
|
+
if (!Array.isArray(errors) || errors.length === 0) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return errors.map(toOperationErrorEntry);
|
|
78
|
+
};
|
|
79
|
+
var toOperationError = function toOperationError(operationErrors, status) {
|
|
80
|
+
var _error$extensions, _error$statusCode;
|
|
81
|
+
var consentError = operationErrors.find(function (error) {
|
|
82
|
+
return getStringExtension(error.extensions, 'errorType') === USER_CONSENT_REQUIRED || !!getAuthUrl(error.extensions);
|
|
83
|
+
});
|
|
84
|
+
var error = consentError !== null && consentError !== void 0 ? consentError : operationErrors[0];
|
|
85
|
+
var authUrl = getAuthUrl(consentError === null || consentError === void 0 ? void 0 : consentError.extensions);
|
|
86
|
+
var retryable = (_error$extensions = error.extensions) === null || _error$extensions === void 0 ? void 0 : _error$extensions.retryable;
|
|
87
|
+
var options = {
|
|
88
|
+
authUrl: authUrl,
|
|
89
|
+
errorType: getStringExtension(error.extensions, 'errorType'),
|
|
90
|
+
retryable: typeof retryable === 'boolean' ? retryable : undefined,
|
|
91
|
+
statusCode: (_error$statusCode = error.statusCode) !== null && _error$statusCode !== void 0 ? _error$statusCode : status,
|
|
92
|
+
operationErrors: operationErrors
|
|
93
|
+
};
|
|
94
|
+
if (consentError && authUrl) {
|
|
95
|
+
return new IntegrationActionError('consent-required', error.message, options);
|
|
96
|
+
}
|
|
97
|
+
return new IntegrationActionError('operation', error.message, options);
|
|
98
|
+
};
|
|
99
|
+
var toGraphQLError = function toGraphQLError(graphQLErrors, statusCode) {
|
|
100
|
+
var authUrl = graphQLErrors.map(function (error) {
|
|
101
|
+
return getAuthUrl(error.extensions);
|
|
102
|
+
}).find(function (url) {
|
|
103
|
+
return !!url;
|
|
104
|
+
});
|
|
105
|
+
var errorType = graphQLErrors.map(function (error) {
|
|
106
|
+
var _getStringExtension;
|
|
107
|
+
return (_getStringExtension = getStringExtension(error.extensions, 'errorType')) !== null && _getStringExtension !== void 0 ? _getStringExtension : getStringExtension(error.extensions, 'classification');
|
|
108
|
+
}).find(function (type) {
|
|
109
|
+
return !!type;
|
|
110
|
+
});
|
|
111
|
+
var message = graphQLErrors.map(function (error) {
|
|
112
|
+
return error.message;
|
|
113
|
+
}).join('; ');
|
|
114
|
+
var options = {
|
|
115
|
+
authUrl: authUrl,
|
|
116
|
+
errorType: errorType,
|
|
117
|
+
statusCode: statusCode,
|
|
118
|
+
graphQLErrors: graphQLErrors
|
|
119
|
+
};
|
|
120
|
+
if (authUrl) {
|
|
121
|
+
return new IntegrationActionError('consent-required', message, options);
|
|
122
|
+
}
|
|
123
|
+
return new IntegrationActionError('graphql', message, options);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Turns an AGG response body into the action response, or throws an `IntegrationActionError`.
|
|
128
|
+
*
|
|
129
|
+
* Errors are checked in this order:
|
|
130
|
+
* 1. IS operation errors in `outputs.errors` (consent required, or the action failed).
|
|
131
|
+
* 2. GraphQL `errors`, even with a partial `execute` result. The mutation only selects
|
|
132
|
+
* `actions.execute`, so any GraphQL error relates to the action.
|
|
133
|
+
* 3. A missing `execute` result.
|
|
134
|
+
* 4. A non-2xx `status` from IS.
|
|
135
|
+
*/
|
|
136
|
+
export var parseExecuteIntegrationActionResponse = function parseExecuteIntegrationActionResponse(body, httpStatus) {
|
|
137
|
+
var _body$data;
|
|
138
|
+
var execute = body === null || body === void 0 || (_body$data = body.data) === null || _body$data === void 0 || (_body$data = _body$data.actions) === null || _body$data === void 0 ? void 0 : _body$data.execute;
|
|
139
|
+
var graphQLErrors = Array.isArray(body === null || body === void 0 ? void 0 : body.errors) && body.errors.length ? body.errors.map(toGraphQLErrorEntry) : undefined;
|
|
140
|
+
var operationErrors = getOperationErrors(execute === null || execute === void 0 ? void 0 : execute.outputs);
|
|
141
|
+
if (operationErrors !== null && operationErrors !== void 0 && operationErrors.length) {
|
|
142
|
+
throw toOperationError(operationErrors, execute === null || execute === void 0 ? void 0 : execute.status);
|
|
143
|
+
}
|
|
144
|
+
if (graphQLErrors) {
|
|
145
|
+
throw toGraphQLError(graphQLErrors, httpStatus);
|
|
146
|
+
}
|
|
147
|
+
if (!execute) {
|
|
148
|
+
throw new IntegrationActionError('graphql', 'No action result returned', {
|
|
149
|
+
statusCode: httpStatus
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (typeof execute.status === 'number' && !isSuccessStatus(execute.status)) {
|
|
153
|
+
throw new IntegrationActionError('operation', "Action failed with status ".concat(execute.status), {
|
|
154
|
+
statusCode: execute.status
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
status: execute.status,
|
|
159
|
+
outputs: execute.outputs
|
|
160
|
+
};
|
|
161
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProductType } from '@atlaskit/linking-common/types';
|
|
2
|
+
type BuildRequestHeadersOptions = {
|
|
3
|
+
/** Cloud ID of the current site. Sent as `tenantId`, or `null` when unknown. */
|
|
4
|
+
cloudId?: string;
|
|
5
|
+
/** Product hosting the Smart Link, mapped to a GTM product name. */
|
|
6
|
+
product?: ProductType;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The `atl-attribution` header AGG asks frontend clients to send for metrics attribution:
|
|
10
|
+
* JSON with `tenantId`, `product` (when known) and `service`.
|
|
11
|
+
*
|
|
12
|
+
* See https://hello.atlassian.net/wiki/spaces/aggp/pages/5954141702
|
|
13
|
+
*/
|
|
14
|
+
export declare const buildRequestHeaders: ({ cloudId, product, }: BuildRequestHeadersOptions) => Record<string, string>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const EXECUTE_INTEGRATION_ACTION_OPERATION_NAME = "LinkClientExtension_ExecuteIntegrationAction";
|
|
2
|
+
export declare const EXECUTE_INTEGRATION_ACTION_MUTATION: string;
|
|
3
|
+
export declare const USER_CONSENT_REQUIRED = "USER_CONSENT_REQUIRED";
|
|
4
|
+
/** `service` value for the AGG `atl-attribution` header. */
|
|
5
|
+
export declare const ATTRIBUTION_SERVICE = "link-client-extension";
|
|
6
|
+
/** Fallback message when IS reports an error without one. */
|
|
7
|
+
export declare const GENERIC_OPERATION_ERROR_MESSAGE = "Action failed";
|
|
8
|
+
/** Fallback message when AGG returns a GraphQL error without one. */
|
|
9
|
+
export declare const GENERIC_GRAPHQL_ERROR_MESSAGE = "GraphQL error";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { EnvironmentsKeys } from '@atlaskit/linking-common/types';
|
|
2
|
+
/**
|
|
3
|
+
* AGG endpoint for the CardClient's environment.
|
|
4
|
+
* - No envKey (running inside a product): same-origin `/gateway/api/graphql`. On the server there
|
|
5
|
+
* is no `window`, so this is relative and requests fail with an `http` error instead of crashing.
|
|
6
|
+
* - `custom`: `baseUrlOverride` is a full object resolver URL (e.g. a direct service URL for SSR),
|
|
7
|
+
* so we can't build an AGG URL from it. Use the relative edge proxy path instead, the same
|
|
8
|
+
* fallback `getResolverUrl` uses.
|
|
9
|
+
* - Other envKeys (e.g. `stg` in examples): the Stargate base URL for that environment + `/graphql`,
|
|
10
|
+
* e.g. `https://pug.jira-dev.com/gateway/api/graphql`.
|
|
11
|
+
* The operation name is added as a query param so requests are easy to identify.
|
|
12
|
+
*/
|
|
13
|
+
export declare const getAggUrl: (envKey?: EnvironmentsKeys) => string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type CardClient from '@atlaskit/link-provider/client';
|
|
2
|
+
import type { ProductType } from '@atlaskit/linking-common/types';
|
|
3
|
+
import type { ExecuteIntegrationActionRequest, ExecuteIntegrationActionResponse } from './types';
|
|
4
|
+
type UseIntegrationActionsClientOptions = {
|
|
5
|
+
/** Product hosting the Smart Link. Sent to AGG in the `atl-attribution` header. */
|
|
6
|
+
product?: ProductType;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* useIntegrationActionsClient
|
|
10
|
+
*
|
|
11
|
+
* Runs Integrations Service (IS) actions on 3P resources (e.g. listing or adding Google Docs
|
|
12
|
+
* comments) through the AGG `actions { execute }` mutation. Uses a plain fetch rather than Relay,
|
|
13
|
+
* so it works in any host that provides a CardClient.
|
|
14
|
+
*
|
|
15
|
+
* `executeAction` resolves with the action's `outputs`. It throws an `IntegrationActionError` when:
|
|
16
|
+
* - the user needs to connect their 3P account (`kind: 'consent-required'`, with `authUrl`)
|
|
17
|
+
* - IS reports the action failed (`kind: 'operation'`)
|
|
18
|
+
* - AGG returns GraphQL errors (`kind: 'graphql'`)
|
|
19
|
+
* - the request fails (`kind: 'http'`)
|
|
20
|
+
*
|
|
21
|
+
* Each request sends the AGG metrics attribution header (`atl-attribution`), using
|
|
22
|
+
* `actionInput.cloudId` as the tenant.
|
|
23
|
+
*
|
|
24
|
+
* Call `executeAction` from effects or event handlers, not during render (it doesn't run in SSR).
|
|
25
|
+
*
|
|
26
|
+
* @param cardClient used to pick the AGG endpoint for the environment
|
|
27
|
+
* @param options.product product hosting the Smart Link, for metrics attribution
|
|
28
|
+
*/
|
|
29
|
+
export declare const useIntegrationActionsClient: (cardClient: CardClient, { product }?: UseIntegrationActionsClientOptions) => {
|
|
30
|
+
executeAction: <TOutputs = unknown>(data: ExecuteIntegrationActionRequest) => Promise<ExecuteIntegrationActionResponse<TOutputs>>;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IntegrationActionErrorKind, IntegrationActionGraphQLError, IntegrationActionOperationError } from './types';
|
|
2
|
+
export type IntegrationActionErrorOptions = {
|
|
3
|
+
authUrl?: string;
|
|
4
|
+
errorType?: string;
|
|
5
|
+
graphQLErrors?: IntegrationActionGraphQLError[];
|
|
6
|
+
operationErrors?: IntegrationActionOperationError[];
|
|
7
|
+
/** The underlying error, e.g. the network `TypeError` for a failed fetch. */
|
|
8
|
+
originalError?: unknown;
|
|
9
|
+
retryable?: boolean;
|
|
10
|
+
statusCode?: number;
|
|
11
|
+
};
|
|
12
|
+
/** Thrown by `executeAction` when an action can't be run or fails. */
|
|
13
|
+
export declare class IntegrationActionError extends Error {
|
|
14
|
+
readonly kind: IntegrationActionErrorKind;
|
|
15
|
+
/** Set when `kind` is `consent-required`: where the user can connect their 3P account. */
|
|
16
|
+
readonly authUrl?: string;
|
|
17
|
+
readonly errorType?: string;
|
|
18
|
+
readonly retryable?: boolean;
|
|
19
|
+
/** HTTP status, or the status IS reported for the action. */
|
|
20
|
+
readonly statusCode?: number;
|
|
21
|
+
readonly graphQLErrors?: IntegrationActionGraphQLError[];
|
|
22
|
+
readonly operationErrors?: IntegrationActionOperationError[];
|
|
23
|
+
/** The underlying error, when the request itself failed. */
|
|
24
|
+
readonly originalError?: unknown;
|
|
25
|
+
constructor(kind: IntegrationActionErrorKind, message: string, options?: IntegrationActionErrorOptions);
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ExecuteIntegrationActionGraphQLResponse } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Mock AGG `actions { execute }` responses, based on what the ASIMO-5527 spike saw
|
|
4
|
+
* on staging.
|
|
5
|
+
*/
|
|
6
|
+
export declare const mockExecuteIntegrationActionSuccessResponse: ExecuteIntegrationActionGraphQLResponse;
|
|
7
|
+
export declare const mockAuthUrl = "https://id.atlassian.com/outboundAuth/start?key=google-drive";
|
|
8
|
+
/** IS returns consent errors in `outputs.errors` with HTTP 200 and no GraphQL `errors`. */
|
|
9
|
+
export declare const mockExecuteIntegrationActionConsentRequiredResponse: ExecuteIntegrationActionGraphQLResponse;
|
|
10
|
+
export declare const mockExecuteIntegrationActionOperationFailureResponse: ExecuteIntegrationActionGraphQLResponse;
|
|
11
|
+
export declare const mockExecuteIntegrationActionGraphQLErrorResponse: ExecuteIntegrationActionGraphQLResponse;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExecuteIntegrationActionGraphQLResponse, ExecuteIntegrationActionResponse } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Turns an AGG response body into the action response, or throws an `IntegrationActionError`.
|
|
4
|
+
*
|
|
5
|
+
* Errors are checked in this order:
|
|
6
|
+
* 1. IS operation errors in `outputs.errors` (consent required, or the action failed).
|
|
7
|
+
* 2. GraphQL `errors`, even with a partial `execute` result. The mutation only selects
|
|
8
|
+
* `actions.execute`, so any GraphQL error relates to the action.
|
|
9
|
+
* 3. A missing `execute` result.
|
|
10
|
+
* 4. A non-2xx `status` from IS.
|
|
11
|
+
*/
|
|
12
|
+
export declare const parseExecuteIntegrationActionResponse: <TOutputs = unknown>(body: ExecuteIntegrationActionGraphQLResponse | undefined, httpStatus?: number) => ExecuteIntegrationActionResponse<TOutputs>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for executing Integrations Service (IS) actions through the Atlassian GraphQL Gateway (AGG)
|
|
3
|
+
* `actions { execute }` mutation.
|
|
4
|
+
*
|
|
5
|
+
* Internal for now. Move these to `@atlaskit/linking-types` (e.g. an `integration-actions` entry)
|
|
6
|
+
* once there is a consumer outside this package.
|
|
7
|
+
*/
|
|
8
|
+
/** Mirrors AGG `ActionsAuthType`. */
|
|
9
|
+
export type IntegrationActionAuthType =
|
|
10
|
+
/** Runs with a user in context. */
|
|
11
|
+
'THREE_LEGGED'
|
|
12
|
+
/** Runs without a user in context. */
|
|
13
|
+
| 'TWO_LEGGED';
|
|
14
|
+
/** Selects which action IS should run. Mirrors AGG `ActionsExecuteActionFilter`. */
|
|
15
|
+
export type IntegrationActionFilter = {
|
|
16
|
+
/** Run the action with this ARI. Don't set `actionId` as well. */
|
|
17
|
+
actionAri?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Run the action with this ID, e.g. for custom-alias actions with
|
|
20
|
+
* `actionTypeKey: 'atlassian::custom:action'`. Don't set `actionAri` as well.
|
|
21
|
+
*/
|
|
22
|
+
actionId?: string;
|
|
23
|
+
/** Only run actions that support these auth types. */
|
|
24
|
+
authType?: IntegrationActionAuthType[];
|
|
25
|
+
/** Only run the action that matches this extension ARI. */
|
|
26
|
+
extensionAri?: string;
|
|
27
|
+
/** Only run actions for this first-party integration, e.g. `google-drive` or `github`. */
|
|
28
|
+
integrationKey?: string;
|
|
29
|
+
/** Only run actions for this OAuth client. */
|
|
30
|
+
oauthClientId?: string;
|
|
31
|
+
/** Only run actions for this provider ARI. */
|
|
32
|
+
providerAri?: string;
|
|
33
|
+
/** Only run actions for this provider ID. */
|
|
34
|
+
providerId?: string;
|
|
35
|
+
};
|
|
36
|
+
/** The resource the action runs against. Mirrors AGG `ActionsExecuteTargetInput`. */
|
|
37
|
+
export type IntegrationActionTarget = {
|
|
38
|
+
ari?: string;
|
|
39
|
+
/** Provider-specific IDs that identify the resource. */
|
|
40
|
+
ids?: Record<string, unknown>;
|
|
41
|
+
url?: string;
|
|
42
|
+
};
|
|
43
|
+
/** Mirrors AGG `ActionsExecuteActionInput`. */
|
|
44
|
+
export type IntegrationActionInput = {
|
|
45
|
+
/** Cloud ID of the site to run the action on. */
|
|
46
|
+
cloudId?: string;
|
|
47
|
+
/** Action-specific inputs, e.g. `{ fileId, content }` for adding a Google Docs comment. */
|
|
48
|
+
inputs?: Record<string, unknown>;
|
|
49
|
+
/** Runtime metadata from the caller, kept separate from the action inputs. */
|
|
50
|
+
meta?: Record<string, unknown>;
|
|
51
|
+
target?: IntegrationActionTarget;
|
|
52
|
+
};
|
|
53
|
+
export type ExecuteIntegrationActionRequest = {
|
|
54
|
+
actionInput: IntegrationActionInput;
|
|
55
|
+
/**
|
|
56
|
+
* Standard action type key, or `atlassian::custom:action` for custom-alias actions
|
|
57
|
+
* (set `filter.actionId` as well).
|
|
58
|
+
*/
|
|
59
|
+
actionTypeKey?: string;
|
|
60
|
+
filter: IntegrationActionFilter;
|
|
61
|
+
workspace?: string;
|
|
62
|
+
};
|
|
63
|
+
export type ExecuteIntegrationActionResponse<TOutputs = unknown> = {
|
|
64
|
+
/** Outputs from the app that ran the action. The shape depends on the action. */
|
|
65
|
+
outputs: TOutputs;
|
|
66
|
+
/** Status IS reports for the action (HTTP-like, e.g. 200). */
|
|
67
|
+
status?: number;
|
|
68
|
+
};
|
|
69
|
+
/** A GraphQL error from AGG, in the top-level `errors` array. */
|
|
70
|
+
export type IntegrationActionGraphQLError = {
|
|
71
|
+
extensions?: Record<string, unknown>;
|
|
72
|
+
message: string;
|
|
73
|
+
path?: (string | number)[];
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* An error IS reports in `data.actions.execute.outputs.errors`.
|
|
77
|
+
* These come back with HTTP 200 and no GraphQL `errors`.
|
|
78
|
+
*/
|
|
79
|
+
export type IntegrationActionOperationError = {
|
|
80
|
+
extensions?: {
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
/** URL where the user can connect, or reconnect, their 3P account. */
|
|
83
|
+
authUrl?: string;
|
|
84
|
+
errorCategory?: string;
|
|
85
|
+
/** e.g. `USER_CONSENT_REQUIRED` */
|
|
86
|
+
errorType?: string;
|
|
87
|
+
retryable?: boolean;
|
|
88
|
+
};
|
|
89
|
+
message: string;
|
|
90
|
+
statusCode?: number;
|
|
91
|
+
};
|
|
92
|
+
export type IntegrationActionErrorKind =
|
|
93
|
+
/** The user must connect, or reconnect, their 3P account. See `authUrl`. */
|
|
94
|
+
'consent-required'
|
|
95
|
+
/** IS ran the action and it failed. */
|
|
96
|
+
| 'operation'
|
|
97
|
+
/** AGG returned GraphQL errors, or the response was malformed. */
|
|
98
|
+
| 'graphql'
|
|
99
|
+
/** The request failed: non-2xx response, network error, or non-JSON body. */
|
|
100
|
+
| 'http';
|
|
101
|
+
/** Raw AGG response body for the execute mutation. */
|
|
102
|
+
export type ExecuteIntegrationActionGraphQLResponse = {
|
|
103
|
+
data?: {
|
|
104
|
+
actions?: {
|
|
105
|
+
execute?: {
|
|
106
|
+
outputs?: unknown;
|
|
107
|
+
status?: number;
|
|
108
|
+
} | null;
|
|
109
|
+
} | null;
|
|
110
|
+
} | null;
|
|
111
|
+
errors?: IntegrationActionGraphQLError[];
|
|
112
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/link-client-extension",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.1",
|
|
4
4
|
"description": "Extension methods for linking platform client",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"importSpecifierName": "isReactCompilerActivePlatform"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
|
-
"team": "
|
|
18
|
+
"team": "Rovo Growth - Smartlinks",
|
|
19
19
|
"website": {
|
|
20
20
|
"tags": [
|
|
21
21
|
"platform-labs"
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"sideEffects": false,
|
|
32
32
|
"atlaskit:src": "src/index.ts",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@atlaskit/json-ld-types": "^2.
|
|
34
|
+
"@atlaskit/json-ld-types": "^2.1.0",
|
|
35
35
|
"@atlaskit/linking-common": "^13.0.0",
|
|
36
|
-
"@atlaskit/linking-types": "^16.
|
|
36
|
+
"@atlaskit/linking-types": "^16.1.0",
|
|
37
37
|
"@atlaskit/platform-feature-flags": "^2.2.0",
|
|
38
38
|
"@atlaskit/react-compiler-gating": "^0.2.0",
|
|
39
39
|
"@babel/runtime": "^7.0.0",
|
|
@@ -44,13 +44,13 @@
|
|
|
44
44
|
"react": "^18.2.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
+
"@atlaskit/link-provider": "^6.0.0",
|
|
47
48
|
"@atlaskit/link-test-helpers": "^11.2.0",
|
|
48
49
|
"@atlassian/feature-flags-test-utils": "^1.3.0",
|
|
49
50
|
"@atlassian/structured-docs-types": "workspace:^",
|
|
50
51
|
"@testing-library/react": "^16.3.0",
|
|
51
52
|
"react": "^18.2.0",
|
|
52
|
-
"react-dom": "^18.2.0"
|
|
53
|
-
"wait-for-expect": "^1.2.0"
|
|
53
|
+
"react-dom": "^18.2.0"
|
|
54
54
|
},
|
|
55
55
|
"compassUnitTestMetricSourceId": "ari:cloud:compass:a436116f-02ce-4520-8fbb-7301462a1674:metric-source/c5751cc6-3513-4070-9deb-af31e86aed34/cf4080bb-133c-425e-bf40-17e725f5add5"
|
|
56
56
|
}
|