@gitkraken/core-gitlens 0.16.1 → 0.16.3
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 +16 -1
- package/dist/git/watching/classifyChange.js +5 -2
- package/dist/git/watching/classifyChange.js.map +1 -1
- package/dist/git/watching/watchGroup.js +2 -1
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +33 -9
- package/dist/git-cli/providers/branches.js +124 -41
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +1 -0
- package/dist/plus/integrations/integrationService.js +18 -5
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
- package/dist/plus/integrations/providers/apiClients.js +12 -3
- package/dist/plus/integrations/providers/apiClients.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +13 -11
- package/dist/plus/integrations/providers/azure/azure.js +128 -47
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +4 -10
- package/dist/plus/integrations/providers/azure/models.js +25 -9
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemDescriptions.d.ts +10 -0
- package/dist/plus/integrations/providers/azure/workItemDescriptions.js +48 -0
- package/dist/plus/integrations/providers/azure/workItemDescriptions.js.map +1 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +20 -19
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +65 -40
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
- package/dist/plus/integrations/providers/providerApiClient.d.ts +9 -0
- package/dist/plus/integrations/providers/providerApiClient.js +66 -0
- package/dist/plus/integrations/providers/providerApiClient.js.map +1 -0
- package/dist/plus/integrations/providers/providersApi.d.ts +10 -9
- package/dist/plus/integrations/providers/providersApi.js +163 -145
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +0 -1
- package/docs/integrations.md +30 -13
- package/package.json +2 -2
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { IssueSorting } from "../../../git/models/issue.js";
|
|
2
|
-
import "../providerFilters.js";
|
|
3
2
|
//#region ../plus/integrations/src/providers/issueSorts.d.ts
|
|
4
3
|
/** GitHub and GHE: one `search` channel serves all three issue reads, so all three order the same way. */
|
|
5
4
|
export declare const githubIssueSorts: IssueSorting[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ProviderRequestFunction, ProviderRequestResponse } from "./models.js";
|
|
2
|
+
import ProviderApis from "@gitkraken/provider-apis";
|
|
3
|
+
//#region ../plus/integrations/src/providers/providerApiClient.d.ts
|
|
4
|
+
type ProviderApisFactory = typeof ProviderApis;
|
|
5
|
+
export declare const createProviderApis: ProviderApisFactory;
|
|
6
|
+
export declare function createProviderApiRequest(fetch: (input: string | URL, init?: RequestInit) => Promise<Response>, userAgent?: string): ProviderRequestFunction;
|
|
7
|
+
export declare function parseFetchResponseForApi<T>(response: Response, sentCredential?: () => boolean): Promise<ProviderRequestResponse<T>>;
|
|
8
|
+
//#endregion
|
|
9
|
+
//# sourceMappingURL=providerApiClient.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getAuthenticatedUser } from "../errors.js";
|
|
2
|
+
import { AnonymousResponseError, UnexpectedHtmlResponseError, getProviderResponseBodyMessage, markRequestError } from "./providerErrors.js";
|
|
3
|
+
import ProviderApis from "@gitkraken/provider-apis";
|
|
4
|
+
//#region ../plus/integrations/src/providers/providerApiClient.ts
|
|
5
|
+
const createProviderApis = ProviderApis.default ?? ProviderApis;
|
|
6
|
+
function createProviderApiRequest(fetch, userAgent) {
|
|
7
|
+
return async ({ url, ...options }) => {
|
|
8
|
+
try {
|
|
9
|
+
return await parseFetchResponseForApi(await fetch(url, {
|
|
10
|
+
...options,
|
|
11
|
+
headers: {
|
|
12
|
+
...userAgent != null ? { "User-Agent": userAgent } : {},
|
|
13
|
+
...options.headers
|
|
14
|
+
}
|
|
15
|
+
}), () => hasAuthorization(options.headers));
|
|
16
|
+
} catch (ex) {
|
|
17
|
+
throw markRequestError(ex);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function parseFetchResponseForApi(response, sentCredential) {
|
|
22
|
+
const contentType = (response.headers.get("content-type") || "").toLowerCase();
|
|
23
|
+
if (response.status !== 401 && response.status !== 429 && response.status < 500 && getAuthenticatedUser(response.headers) === "anonymous" && sentCredential?.() === true) {
|
|
24
|
+
response.body?.cancel().catch(() => void 0);
|
|
25
|
+
throw new AnonymousResponseError(response.status, {
|
|
26
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
27
|
+
status: response.status
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
let body;
|
|
31
|
+
let servedHtml = false;
|
|
32
|
+
if (contentType.startsWith("application/json")) {
|
|
33
|
+
const text = await response.text();
|
|
34
|
+
if (text.trimStart().startsWith("<")) {
|
|
35
|
+
servedHtml = true;
|
|
36
|
+
body = text;
|
|
37
|
+
} else body = text.trim().length > 0 ? JSON.parse(text) : null;
|
|
38
|
+
} else if (contentType.startsWith("text/") || contentType === "") {
|
|
39
|
+
body = await response.text();
|
|
40
|
+
servedHtml = contentType.startsWith("text/html") && body.trim().length > 0;
|
|
41
|
+
} else if (contentType.startsWith("application/vnd.github.raw+json")) body = await response.arrayBuffer();
|
|
42
|
+
else throw new Error(`Unsupported content-type: ${contentType}`);
|
|
43
|
+
const result = {
|
|
44
|
+
body,
|
|
45
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
46
|
+
status: response.status,
|
|
47
|
+
statusText: response.statusText
|
|
48
|
+
};
|
|
49
|
+
if (response.ok && servedHtml) throw new UnexpectedHtmlResponseError(response.status, contentType, result);
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const status = `(${response.status})${response.statusText ? ` ${response.statusText}` : ""}.`;
|
|
52
|
+
const detail = getProviderResponseBodyMessage(body);
|
|
53
|
+
const error = new Error(detail != null ? `${status} ${detail}` : status);
|
|
54
|
+
Object.assign(error, { response: result });
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
function hasAuthorization(headers) {
|
|
60
|
+
if (headers == null) return false;
|
|
61
|
+
return Object.entries(headers).some(([name, value]) => name.toLowerCase() === "authorization" && Boolean(value));
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { createProviderApiRequest, createProviderApis, parseFetchResponseForApi };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=providerApiClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providerApiClient.js","names":[],"sources":["../../../../../plus/integrations/src/providers/providerApiClient.ts"],"sourcesContent":["import ProviderApis from '@gitkraken/provider-apis';\nimport { getAuthenticatedUser } from '../errors.js';\nimport type { ProviderRequestFunction, ProviderRequestOptions, ProviderRequestResponse } from './models.js';\nimport {\n\tAnonymousResponseError,\n\tgetProviderResponseBodyMessage,\n\tmarkRequestError,\n\tUnexpectedHtmlResponseError,\n} from './providerErrors.js';\n\n// `@gitkraken/provider-apis` is published as CommonJS with its factory on the `default` export.\n// How that surfaces depends on the consuming bundler's CJS->ESM interop: esbuild yields the\n// callable factory directly, while webpack and Node surface the module namespace (with the\n// factory under `.default`). Normalize to the callable factory so every consumer resolves it.\ntype ProviderApisFactory = typeof ProviderApis;\nexport const createProviderApis: ProviderApisFactory =\n\t(ProviderApis as ProviderApisFactory & { default?: ProviderApisFactory }).default ?? ProviderApis;\n\nexport function createProviderApiRequest(\n\tfetch: (input: string | URL, init?: RequestInit) => Promise<Response>,\n\tuserAgent?: string,\n): ProviderRequestFunction {\n\treturn async <T>({ url, ...options }: ProviderRequestOptions): Promise<ProviderRequestResponse<T>> => {\n\t\ttry {\n\t\t\tconst response = await fetch(url, {\n\t\t\t\t...options,\n\t\t\t\theaders: { ...(userAgent != null ? { 'User-Agent': userAgent } : {}), ...options.headers },\n\t\t\t});\n\t\t\treturn await parseFetchResponseForApi<T>(response, () => hasAuthorization(options.headers));\n\t\t} catch (ex) {\n\t\t\tthrow markRequestError(ex);\n\t\t}\n\t};\n}\n\nexport async function parseFetchResponseForApi<T>(\n\tresponse: Response,\n\tsentCredential?: () => boolean,\n): Promise<ProviderRequestResponse<T>> {\n\t// Media types are case-insensitive (RFC 9110 §8.3.1) and `fetch` hands the header back exactly as the server\n\t// wrote it, so match on a lowercased copy rather than the raw value.\n\tconst contentType = (response.headers.get('content-type') || '').toLowerCase();\n\n\t// A credential the server served as the anonymous user was rejected, whatever the body says; reading it as data\n\t// reports an account with nothing in it (see `AnonymousResponseError`). Checked before the body is parsed, which\n\t// may itself fail. A 401 already fails as the credential's rejection, with its body, and a throttle or a server\n\t// failure keeps its own error: neither proves anything about the credential.\n\tif (\n\t\tresponse.status !== 401 &&\n\t\tresponse.status !== 429 &&\n\t\tresponse.status < 500 &&\n\t\tgetAuthenticatedUser(response.headers) === 'anonymous' &&\n\t\tsentCredential?.() === true\n\t) {\n\t\t// Released rather than left for the collector: an unread body holds its connection.\n\t\tvoid response.body?.cancel().catch(() => undefined);\n\t\tthrow new AnonymousResponseError(response.status, {\n\t\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\t\tstatus: response.status,\n\t\t});\n\t}\n\n\tlet body;\n\tlet servedHtml = false;\n\n\t// parse the response body\n\tif (contentType.startsWith('application/json')) {\n\t\tconst text = await response.text();\n\t\t// A sign-in page a server mislabels as JSON is the same page under another header, and `<` can never open\n\t\t// valid JSON — so recognize it rather than letting JSON.parse throw a SyntaxError carrying no status.\n\t\tif (text.trimStart().startsWith('<')) {\n\t\t\tservedHtml = true;\n\t\t\tbody = text;\n\t\t} else {\n\t\t\tbody = text.trim().length > 0 ? JSON.parse(text) : null;\n\t\t}\n\t} else if (contentType.startsWith('text/') || contentType === '') {\n\t\tbody = await response.text();\n\t\t// An empty body is never a sign-in page, and a write answering `204` can carry a stale `text/html` from\n\t\t// whatever its endpoint usually returns — so require actual markup, not just the header.\n\t\tservedHtml = contentType.startsWith('text/html') && body.trim().length > 0;\n\t} else if (contentType.startsWith('application/vnd.github.raw+json')) {\n\t\tbody = await response.arrayBuffer();\n\t} else {\n\t\tthrow new Error(`Unsupported content-type: ${contentType}`);\n\t}\n\n\tconst result = {\n\t\tbody: body,\n\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\tstatus: response.status,\n\t\tstatusText: response.statusText,\n\t};\n\n\t// A 2xx carrying a page rather than data is a rejected credential wearing a success status (GKDEV-3617); a\n\t// non-2xx keeps its own status error below, which is the better diagnostic.\n\tif (response.ok && servedHtml) {\n\t\tthrow new UnexpectedHtmlResponseError(response.status, contentType, result);\n\t}\n\n\t// throw an error if the response is not ok\n\tif (!response.ok) {\n\t\tconst status = `(${response.status})${response.statusText ? ` ${response.statusText}` : ''}.`;\n\t\tconst detail = getProviderResponseBodyMessage(body);\n\t\tconst error = new Error(detail != null ? `${status} ${detail}` : status);\n\t\tObject.assign(error, { response: result });\n\t\tthrow error;\n\t}\n\n\treturn result;\n}\n\nfunction hasAuthorization(headers: ProviderRequestOptions['headers']): boolean {\n\tif (headers == null) return false;\n\n\treturn Object.entries(headers).some(([name, value]) => name.toLowerCase() === 'authorization' && Boolean(value));\n}\n"],"mappings":";;;;AAeA,MAAa,qBACX,aAAyE,WAAW;AAEtF,SAAgB,yBACf,OACA,WAC0B;CAC1B,OAAO,OAAU,EAAE,KAAK,GAAG,cAA2E;EACrG,IAAI;GAKH,OAAO,MAAM,yBAA4B,MAJlB,MAAM,KAAK;IACjC,GAAG;IACH,SAAS;KAAE,GAAI,aAAa,OAAO,EAAE,cAAc,UAAU,IAAI,CAAC;KAAI,GAAG,QAAQ;IAAQ;GAC1F,CAAC,SACwD,iBAAiB,QAAQ,OAAO,CAAC;EAC3F,SAAS,IAAI;GACZ,MAAM,iBAAiB,EAAE;EAC1B;CACD;AACD;AAEA,eAAsB,yBACrB,UACA,gBACsC;CAGtC,MAAM,eAAe,SAAS,QAAQ,IAAI,cAAc,KAAK,GAAA,CAAI,YAAY;CAM7E,IACC,SAAS,WAAW,OACpB,SAAS,WAAW,OACpB,SAAS,SAAS,OAClB,qBAAqB,SAAS,OAAO,MAAM,eAC3C,iBAAiB,MAAM,MACtB;EAED,SAAc,MAAM,OAAO,CAAC,CAAC,YAAY,KAAA,CAAS;EAClD,MAAM,IAAI,uBAAuB,SAAS,QAAQ;GACjD,SAAS,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC;GACtD,QAAQ,SAAS;EAClB,CAAC;CACF;CAEA,IAAI;CACJ,IAAI,aAAa;CAGjB,IAAI,YAAY,WAAW,kBAAkB,GAAG;EAC/C,MAAM,OAAO,MAAM,SAAS,KAAK;EAGjC,IAAI,KAAK,UAAU,CAAC,CAAC,WAAW,GAAG,GAAG;GACrC,aAAa;GACb,OAAO;EACR,OACC,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,KAAK,MAAM,IAAI,IAAI;CAErD,OAAO,IAAI,YAAY,WAAW,OAAO,KAAK,gBAAgB,IAAI;EACjE,OAAO,MAAM,SAAS,KAAK;EAG3B,aAAa,YAAY,WAAW,WAAW,KAAK,KAAK,KAAK,CAAC,CAAC,SAAS;CAC1E,OAAO,IAAI,YAAY,WAAW,iCAAiC,GAClE,OAAO,MAAM,SAAS,YAAY;MAElC,MAAM,IAAI,MAAM,6BAA6B,aAAa;CAG3D,MAAM,SAAS;EACR;EACN,SAAS,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC;EACtD,QAAQ,SAAS;EACjB,YAAY,SAAS;CACtB;CAIA,IAAI,SAAS,MAAM,YAClB,MAAM,IAAI,4BAA4B,SAAS,QAAQ,aAAa,MAAM;CAI3E,IAAI,CAAC,SAAS,IAAI;EACjB,MAAM,SAAS,IAAI,SAAS,OAAO,GAAG,SAAS,aAAa,IAAI,SAAS,eAAe,GAAG;EAC3F,MAAM,SAAS,+BAA+B,IAAI;EAClD,MAAM,QAAQ,IAAI,MAAM,UAAU,OAAO,GAAG,OAAO,GAAG,WAAW,MAAM;EACvE,OAAO,OAAO,OAAO,EAAE,UAAU,OAAO,CAAC;EACzC,MAAM;CACP;CAEA,OAAO;AACR;AAEA,SAAS,iBAAiB,SAAqD;CAC9E,IAAI,WAAW,MAAM,OAAO;CAE5B,OAAO,OAAO,QAAQ,OAAO,CAAC,CAAC,MAAM,CAAC,MAAM,WAAW,KAAK,YAAY,MAAM,mBAAmB,QAAQ,KAAK,CAAC;AAChH"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Disposable } from "../../../utils/disposable.js";
|
|
1
2
|
import { PagedResult } from "../../../utils/paging.js";
|
|
2
3
|
import { IssueSorting } from "../../../git/models/issue.js";
|
|
3
4
|
import { PullRequest, PullRequestFilter, PullRequestMergeMethod } from "../../../git/models/pullRequest.js";
|
|
@@ -6,21 +7,26 @@ import { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId, Integration
|
|
|
6
7
|
import { ProviderOrganization } from "../results.js";
|
|
7
8
|
import { ProviderPullRequestCount, ProviderPullRequestSearchPage } from "../models/pullRequestReads.js";
|
|
8
9
|
import { IssueFilter } from "../providerFilters.js";
|
|
9
|
-
import { GetIssuesOptions, GetPullRequestsForUserOptions, GetPullRequestsOptions, GetReposOptions, JiraIssuePage, PageInfo, PagingInput, PagingMode, ProviderAccount, ProviderApiCollectionResult, ProviderApiPagedResult, ProviderAzureProject, ProviderAzureResource, ProviderBitbucketResource, ProviderGitHubOrganization, ProviderGitLabGroup, ProviderHierarchyResult, ProviderIssue, ProviderJiraProject, ProviderJiraResource, ProviderJiraServerProject, ProviderLinearOrganization, ProviderLinearTeam, ProviderPullRequest, ProviderRepoInput, ProviderReposInput, ProviderRepository
|
|
10
|
+
import { GetIssuesOptions, GetPullRequestsForUserOptions, GetPullRequestsOptions, GetReposOptions, JiraIssuePage, PageInfo, PagingInput, PagingMode, ProviderAccount, ProviderApiCollectionResult, ProviderApiPagedResult, ProviderAzureProject, ProviderAzureResource, ProviderBitbucketResource, ProviderGitHubOrganization, ProviderGitLabGroup, ProviderHierarchyResult, ProviderIssue, ProviderJiraProject, ProviderJiraResource, ProviderJiraServerProject, ProviderLinearOrganization, ProviderLinearTeam, ProviderPullRequest, ProviderRepoInput, ProviderReposInput, ProviderRepository } from "./models.js";
|
|
10
11
|
import { AzurePullRequest, AzureWorkItemEtagRead } from "./azure/models.js";
|
|
11
12
|
import { AzureWorkItemStatesCache } from "./azure/workItemStateCategories.js";
|
|
13
|
+
import { AzureWorkItemDescriptionsCache } from "./azure/workItemDescriptions.js";
|
|
12
14
|
import { countBitbucketServerPullRequests, searchBitbucketServerPullRequestsPage } from "./bitbucket-server/pullRequestSearch.js";
|
|
13
15
|
import { JiraIssueEtagResponse } from "./jiraIssueByKey.js";
|
|
14
16
|
import { LinearIssueEtagNode } from "./linearIssuesEtag.js";
|
|
15
17
|
import { IntegrationAuthenticationService } from "../authentication/integrationAuthenticationService.js";
|
|
16
18
|
import { CollectionMetadata, GitIssueState, GitPullRequestState, TrelloBoard, TrelloList } from "@gitkraken/provider-apis";
|
|
17
19
|
//#region ../plus/integrations/src/providers/providersApi.d.ts
|
|
18
|
-
export declare class ProvidersApi {
|
|
20
|
+
export declare class ProvidersApi implements Disposable {
|
|
19
21
|
private readonly authenticationService;
|
|
20
22
|
private readonly azureWorkItemTypeStates;
|
|
23
|
+
private readonly azureWorkItemDescriptions;
|
|
21
24
|
private readonly providers;
|
|
22
25
|
private readonly request;
|
|
23
|
-
|
|
26
|
+
private azureRestApi;
|
|
27
|
+
private readonly disposable;
|
|
28
|
+
constructor(authenticationService: IntegrationAuthenticationService, azureWorkItemTypeStates?: AzureWorkItemStatesCache, azureWorkItemDescriptions?: AzureWorkItemDescriptionsCache);
|
|
29
|
+
dispose(): void;
|
|
24
30
|
getScopesForProvider(providerId: IntegrationIds): string[] | undefined;
|
|
25
31
|
getProviderDomain(providerId: IntegrationIds): string | undefined;
|
|
26
32
|
getProviderPullRequestsPagingMode(providerId: IntegrationIds): PagingMode | undefined;
|
|
@@ -102,6 +108,7 @@ export declare class ProvidersApi {
|
|
|
102
108
|
isPAT?: boolean;
|
|
103
109
|
baseUrl?: string;
|
|
104
110
|
}): Promise<ProviderIssue | undefined>;
|
|
111
|
+
private getAzureRestResource;
|
|
105
112
|
/**
|
|
106
113
|
* One Azure DevOps work item by id, converted as provider-apis converts its list rows (see
|
|
107
114
|
* {@link fromAzureWorkItemToProviderIssue}). provider-apis has no single work item read, so this asks Azure
|
|
@@ -347,11 +354,6 @@ export declare class ProvidersApi {
|
|
|
347
354
|
}): Promise<ProviderApiPagedResult<ProviderIssue>>;
|
|
348
355
|
private azureStatesScope;
|
|
349
356
|
private readAzureStatesMetadata;
|
|
350
|
-
/**
|
|
351
|
-
* The organization's rich text fields a work item's description may come from, as provider-apis reads them for
|
|
352
|
-
* its list rows. Best-effort: `undefined` on any failure, which leaves the description to the built-in fields
|
|
353
|
-
* rather than failing the work item. Logged for every failure, a body that isn't a catalog included.
|
|
354
|
-
*/
|
|
355
357
|
private getAzureWorkItemHtmlFields;
|
|
356
358
|
private getAzureTypeStates;
|
|
357
359
|
private getAzureProjectWorkItemStates;
|
|
@@ -451,6 +453,5 @@ export declare class ProvidersApi {
|
|
|
451
453
|
baseUrl?: string;
|
|
452
454
|
}): Promise<ProviderIssue | undefined>;
|
|
453
455
|
}
|
|
454
|
-
export declare function parseFetchResponseForApi<T>(response: Response, sentCredential?: () => boolean): Promise<ProviderRequestResponse<T>>;
|
|
455
456
|
//#endregion
|
|
456
457
|
//# sourceMappingURL=providersApi.d.ts.map
|
|
@@ -2,22 +2,23 @@ import { Logger as _logger } from "../../../utils/logger.js";
|
|
|
2
2
|
import { mapSettledBounded } from "../../../utils/promise.js";
|
|
3
3
|
import { RequestNotFoundError } from "../../../git/errors.js";
|
|
4
4
|
import { base64 } from "../../../utils/base64.js";
|
|
5
|
-
import {
|
|
5
|
+
import { toError } from "../errors.js";
|
|
6
6
|
import "../constants.js";
|
|
7
|
-
import {
|
|
7
|
+
import { isAzureProviderId, isProviderIssueNotFoundError, throwProviderError } from "./providerErrors.js";
|
|
8
8
|
import { isIncompleteCollection, toCollectionScopeFailure, withValidSkippedItems } from "../collectionMetadata.js";
|
|
9
9
|
import { toTokenWithInfo } from "../authentication/models.js";
|
|
10
10
|
import { collectProviderPagedResult, mergeCollectionMetadata, parsePageCursor, toPageCursor } from "./utils/providerPaging.js";
|
|
11
11
|
import { isRepoIdsInput, providersMetadata } from "./models.js";
|
|
12
12
|
import { azureIssueStateCategory, azureWorkItemEtagFieldNames, encodeAzurePathSegment, fromAzureWorkItemToProviderIssue, toAzureWorkItemDescriptionFields } from "./azure/models.js";
|
|
13
|
-
import {
|
|
13
|
+
import { AzureWorkItemDescriptionsCache } from "./azure/workItemDescriptions.js";
|
|
14
|
+
import { AzureWorkItemStatesCache, getAzureWorkItemTypeStatesUrl, getAzureWorkItemTypesUrl, requireAzureWorkItemStateCategories, resolveAzureWorkItemStateCategory } from "./azure/workItemStateCategories.js";
|
|
15
|
+
import { createProviderApiRequest, createProviderApis } from "./providerApiClient.js";
|
|
14
16
|
import { requestJiraIssueByKey, requestJiraIssuesEtagFields } from "./jiraIssueByKey.js";
|
|
15
17
|
import { requestLinearIssuesEtagFields } from "./linearIssuesEtag.js";
|
|
16
18
|
import { requestBitbucketServerProjects, requestBitbucketServerRepositories } from "./bitbucket-server/discovery.js";
|
|
17
19
|
import { countBitbucketServerPullRequests, searchBitbucketServerPullRequestsPage } from "./bitbucket-server/pullRequestSearch.js";
|
|
18
|
-
import
|
|
20
|
+
import { GitIssueState } from "@gitkraken/provider-apis";
|
|
19
21
|
//#region ../plus/integrations/src/providers/providersApi.ts
|
|
20
|
-
const createProviderApis = ProviderApis.default ?? ProviderApis;
|
|
21
22
|
const repoNotFoundMessage = /^Repository .+ not found$/i;
|
|
22
23
|
function isGraphQLErrors(ex) {
|
|
23
24
|
return ex instanceof Error && Array.isArray(ex.graphQLErrors);
|
|
@@ -120,31 +121,41 @@ function toJiraIssuePage(result) {
|
|
|
120
121
|
...result.metadata != null ? { metadata: withValidSkippedItems(result.metadata) } : {}
|
|
121
122
|
};
|
|
122
123
|
}
|
|
124
|
+
function createAzureProviderInfo(id, azure) {
|
|
125
|
+
return {
|
|
126
|
+
...providersMetadata[id],
|
|
127
|
+
provider: azure,
|
|
128
|
+
getRepoOfProjectFn: azure.getRepo.bind(azure),
|
|
129
|
+
getCurrentUserFn: azure.getCurrentUser.bind(azure),
|
|
130
|
+
getCurrentUserForInstanceFn: azure.getCurrentUserForInstance.bind(azure),
|
|
131
|
+
getAzureResourcesForUserFn: id === "azureDevOps" ? azure.getOrgsForUser.bind(azure) : azure.getCollectionsForUser.bind(azure),
|
|
132
|
+
getAzureProjectsForResourceFn: azure.getAzureProjects.bind(azure),
|
|
133
|
+
getPullRequestsForReposFn: azure.getPullRequestsForRepos.bind(azure),
|
|
134
|
+
getPullRequestsForRepoFn: azure.getPullRequestsForRepo.bind(azure),
|
|
135
|
+
getPullRequestForRepoFn: azure.getPullRequestForRepo.bind(azure),
|
|
136
|
+
getPullRequestsForAzureProjectsFn: azure.getPullRequestsForProjects.bind(azure),
|
|
137
|
+
getPullRequestsForAzureProjectFn: azure.getPullRequestsForProject.bind(azure),
|
|
138
|
+
getIssuesForAzureProjectFn: azure.getIssuesForAzureProject.bind(azure),
|
|
139
|
+
getReposForAzureProjectFn: azure.getReposForAzureProject.bind(azure),
|
|
140
|
+
mergePullRequestFn: azure.mergePullRequest.bind(azure)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
123
143
|
var ProvidersApi = class {
|
|
124
144
|
authenticationService;
|
|
125
145
|
azureWorkItemTypeStates;
|
|
146
|
+
azureWorkItemDescriptions;
|
|
126
147
|
providers;
|
|
127
148
|
request;
|
|
128
|
-
|
|
149
|
+
azureRestApi;
|
|
150
|
+
disposable;
|
|
151
|
+
constructor(authenticationService, azureWorkItemTypeStates = new AzureWorkItemStatesCache(), azureWorkItemDescriptions = new AzureWorkItemDescriptionsCache()) {
|
|
129
152
|
this.authenticationService = authenticationService;
|
|
130
153
|
this.azureWorkItemTypeStates = azureWorkItemTypeStates;
|
|
154
|
+
this.azureWorkItemDescriptions = azureWorkItemDescriptions;
|
|
131
155
|
const http = authenticationService.ctx.http;
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
return await parseFetchResponseForApi(await http.fetch(url, {
|
|
136
|
-
...options,
|
|
137
|
-
headers: {
|
|
138
|
-
"User-Agent": userAgent,
|
|
139
|
-
...options.headers
|
|
140
|
-
}
|
|
141
|
-
}), () => hasAuthorization(options.headers));
|
|
142
|
-
} catch (ex) {
|
|
143
|
-
throw markRequestError(ex);
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
this.request = customFetch;
|
|
147
|
-
const providerApis = createProviderApis({ request: customFetch });
|
|
156
|
+
this.request = createProviderApiRequest((input, init) => http.fetch(input, init), http.userAgent);
|
|
157
|
+
const providerApis = createProviderApis({ request: this.request });
|
|
158
|
+
this.azureRestApi = providerApis.azureDevOps;
|
|
148
159
|
this.providers = {
|
|
149
160
|
["github"]: {
|
|
150
161
|
...providersMetadata["github"],
|
|
@@ -228,40 +239,8 @@ var ProvidersApi = class {
|
|
|
228
239
|
getPullRequestsForRepoFn: providerApis.bitbucketServer.getPullRequestsForRepo.bind(providerApis.bitbucketServer),
|
|
229
240
|
mergePullRequestFn: providerApis.bitbucketServer.mergePullRequest.bind(providerApis.bitbucketServer)
|
|
230
241
|
},
|
|
231
|
-
["azureDevOps"]:
|
|
232
|
-
|
|
233
|
-
provider: providerApis.azureDevOps,
|
|
234
|
-
getRepoOfProjectFn: providerApis.azureDevOps.getRepo.bind(providerApis.azureDevOps),
|
|
235
|
-
getCurrentUserFn: providerApis.azureDevOps.getCurrentUser.bind(providerApis.azureDevOps),
|
|
236
|
-
getCurrentUserForInstanceFn: providerApis.azureDevOps.getCurrentUserForInstance.bind(providerApis.azureDevOps),
|
|
237
|
-
getAzureResourcesForUserFn: providerApis.azureDevOps.getOrgsForUser.bind(providerApis.azureDevOps),
|
|
238
|
-
getAzureProjectsForResourceFn: providerApis.azureDevOps.getAzureProjects.bind(providerApis.azureDevOps),
|
|
239
|
-
getPullRequestsForReposFn: providerApis.azureDevOps.getPullRequestsForRepos.bind(providerApis.azureDevOps),
|
|
240
|
-
getPullRequestsForRepoFn: providerApis.azureDevOps.getPullRequestsForRepo.bind(providerApis.azureDevOps),
|
|
241
|
-
getPullRequestForRepoFn: providerApis.azureDevOps.getPullRequestForRepo.bind(providerApis.azureDevOps),
|
|
242
|
-
getPullRequestsForAzureProjectsFn: providerApis.azureDevOps.getPullRequestsForProjects.bind(providerApis.azureDevOps),
|
|
243
|
-
getPullRequestsForAzureProjectFn: providerApis.azureDevOps.getPullRequestsForProject.bind(providerApis.azureDevOps),
|
|
244
|
-
getIssuesForAzureProjectFn: providerApis.azureDevOps.getIssuesForAzureProject.bind(providerApis.azureDevOps),
|
|
245
|
-
getReposForAzureProjectFn: providerApis.azureDevOps.getReposForAzureProject.bind(providerApis.azureDevOps),
|
|
246
|
-
mergePullRequestFn: providerApis.azureDevOps.mergePullRequest.bind(providerApis.azureDevOps)
|
|
247
|
-
},
|
|
248
|
-
["azure-devops-server"]: {
|
|
249
|
-
...providersMetadata["azure-devops-server"],
|
|
250
|
-
provider: providerApis.azureDevOps,
|
|
251
|
-
getRepoOfProjectFn: providerApis.azureDevOps.getRepo.bind(providerApis.azureDevOps),
|
|
252
|
-
getCurrentUserFn: providerApis.azureDevOps.getCurrentUser.bind(providerApis.azureDevOps),
|
|
253
|
-
getCurrentUserForInstanceFn: providerApis.azureDevOps.getCurrentUserForInstance.bind(providerApis.azureDevOps),
|
|
254
|
-
getAzureResourcesForUserFn: providerApis.azureDevOps.getCollectionsForUser.bind(providerApis.azureDevOps),
|
|
255
|
-
getAzureProjectsForResourceFn: providerApis.azureDevOps.getAzureProjects.bind(providerApis.azureDevOps),
|
|
256
|
-
getPullRequestsForReposFn: providerApis.azureDevOps.getPullRequestsForRepos.bind(providerApis.azureDevOps),
|
|
257
|
-
getPullRequestsForRepoFn: providerApis.azureDevOps.getPullRequestsForRepo.bind(providerApis.azureDevOps),
|
|
258
|
-
getPullRequestForRepoFn: providerApis.azureDevOps.getPullRequestForRepo.bind(providerApis.azureDevOps),
|
|
259
|
-
getPullRequestsForAzureProjectsFn: providerApis.azureDevOps.getPullRequestsForProjects.bind(providerApis.azureDevOps),
|
|
260
|
-
getPullRequestsForAzureProjectFn: providerApis.azureDevOps.getPullRequestsForProject.bind(providerApis.azureDevOps),
|
|
261
|
-
getIssuesForAzureProjectFn: providerApis.azureDevOps.getIssuesForAzureProject.bind(providerApis.azureDevOps),
|
|
262
|
-
getReposForAzureProjectFn: providerApis.azureDevOps.getReposForAzureProject.bind(providerApis.azureDevOps),
|
|
263
|
-
mergePullRequestFn: providerApis.azureDevOps.mergePullRequest.bind(providerApis.azureDevOps)
|
|
264
|
-
},
|
|
242
|
+
["azureDevOps"]: createAzureProviderInfo("azureDevOps", providerApis.azureDevOps),
|
|
243
|
+
["azure-devops-server"]: createAzureProviderInfo("azure-devops-server", providerApis.azureDevOps),
|
|
265
244
|
["jira"]: {
|
|
266
245
|
...providersMetadata["jira"],
|
|
267
246
|
provider: providerApis.jira,
|
|
@@ -305,6 +284,17 @@ var ProvidersApi = class {
|
|
|
305
284
|
getTrelloLabelsForBoardFn: providerApis.trello.getLabelsForBoard.bind(providerApis.trello)
|
|
306
285
|
}
|
|
307
286
|
};
|
|
287
|
+
this.disposable = authenticationService.ctx.config?.onDidChange((e) => {
|
|
288
|
+
if (!e.httpProxy) return;
|
|
289
|
+
this.azureWorkItemTypeStates.clear();
|
|
290
|
+
this.azureWorkItemDescriptions.clear();
|
|
291
|
+
this.azureRestApi = createProviderApis({ request: this.request }).azureDevOps;
|
|
292
|
+
this.providers["azureDevOps"] = createAzureProviderInfo("azureDevOps", this.azureRestApi);
|
|
293
|
+
this.providers["azure-devops-server"] = createAzureProviderInfo("azure-devops-server", this.azureRestApi);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
dispose() {
|
|
297
|
+
this.disposable?.dispose();
|
|
308
298
|
}
|
|
309
299
|
getScopesForProvider(providerId) {
|
|
310
300
|
return this.providers[providerId]?.scopes;
|
|
@@ -559,10 +549,11 @@ var ProvidersApi = class {
|
|
|
559
549
|
};
|
|
560
550
|
let memberOf;
|
|
561
551
|
try {
|
|
552
|
+
const resource = await this.getAzureRestResource(tokenWithInfo, namespace, "identities", options, "5.0");
|
|
562
553
|
const params = new URLSearchParams({
|
|
563
554
|
identityIds: userId,
|
|
564
555
|
queryMembership: "Expanded",
|
|
565
|
-
"api-version":
|
|
556
|
+
"api-version": resource.apiVersion
|
|
566
557
|
});
|
|
567
558
|
memberOf = (await this.request({
|
|
568
559
|
url: `${base}/_apis/identities?${params.toString()}`,
|
|
@@ -577,9 +568,10 @@ var ProvidersApi = class {
|
|
|
577
568
|
for (let i = 0; i < memberOf.length; i += azureIdentityBatchSize) chunks.push(memberOf.slice(i, i + azureIdentityBatchSize));
|
|
578
569
|
const batches = [];
|
|
579
570
|
try {
|
|
571
|
+
const resource = await this.getAzureRestResource(tokenWithInfo, namespace, "identityBatch", options, "5.0-preview.1");
|
|
580
572
|
for (const descriptors of chunks) {
|
|
581
573
|
const batch = await this.request({
|
|
582
|
-
url: `${base}/_apis/identitybatch?api-version
|
|
574
|
+
url: `${base}/_apis/identitybatch?api-version=${resource.apiVersion}`,
|
|
583
575
|
method: "POST",
|
|
584
576
|
headers,
|
|
585
577
|
body: JSON.stringify({
|
|
@@ -624,6 +616,23 @@ var ProvidersApi = class {
|
|
|
624
616
|
return this.handleProviderError(tokenWithInfo, e);
|
|
625
617
|
}
|
|
626
618
|
}
|
|
619
|
+
async getAzureRestResource(token, namespace, resource, options, version) {
|
|
620
|
+
try {
|
|
621
|
+
encodeAzurePathSegment(namespace);
|
|
622
|
+
const result = await this.azureRestApi.getRestApiResource({
|
|
623
|
+
namespace,
|
|
624
|
+
resource,
|
|
625
|
+
version
|
|
626
|
+
}, {
|
|
627
|
+
...options,
|
|
628
|
+
token: token.accessToken
|
|
629
|
+
});
|
|
630
|
+
if (result == null) throw new Error(`Azure DevOps REST resource ${resource} is unavailable`);
|
|
631
|
+
return result;
|
|
632
|
+
} catch (ex) {
|
|
633
|
+
return this.handleProviderError(token, ex);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
627
636
|
/**
|
|
628
637
|
* One Azure DevOps work item by id, converted as provider-apis converts its list rows (see
|
|
629
638
|
* {@link fromAzureWorkItemToProviderIssue}). provider-apis has no single work item read, so this asks Azure
|
|
@@ -635,30 +644,39 @@ var ProvidersApi = class {
|
|
|
635
644
|
* shape, an empty response, a work item the SDK's conversion would skip, and one in another project.
|
|
636
645
|
*/
|
|
637
646
|
async getAzureWorkItem(tokenOptInfo, scope, id, options) {
|
|
638
|
-
const { tokenWithInfo } = await this.ensureProviderToken(tokenOptInfo);
|
|
647
|
+
const { tokenWithInfo: providedToken } = await this.ensureProviderToken(tokenOptInfo);
|
|
648
|
+
const tokenWithInfo = { ...providedToken };
|
|
639
649
|
const token = tokenWithInfo.accessToken;
|
|
640
650
|
const baseUrl = (options.baseUrl ?? azureDevOpsBaseUrl).replace(/\/$/, "");
|
|
651
|
+
const requestOptions = {
|
|
652
|
+
baseUrl,
|
|
653
|
+
isPAT: options.isPAT ?? false
|
|
654
|
+
};
|
|
655
|
+
const encodedProject = encodeAzurePathSegment(scope.project);
|
|
656
|
+
const resource = await this.getAzureRestResource(tokenWithInfo, scope.namespace, "workItems", requestOptions);
|
|
641
657
|
const params = new URLSearchParams({
|
|
642
658
|
$expand: "Links",
|
|
643
|
-
"api-version":
|
|
659
|
+
"api-version": resource.apiVersion
|
|
644
660
|
});
|
|
645
|
-
const
|
|
661
|
+
const projectPath = resource.projectScoped ? `/${encodedProject}` : "";
|
|
662
|
+
const url = `${baseUrl}/${encodeAzurePathSegment(scope.namespace)}${projectPath}/_apis/wit/workitems/${id}?${params.toString()}`;
|
|
646
663
|
let workItem;
|
|
647
664
|
try {
|
|
648
665
|
workItem = (await this.request({
|
|
649
666
|
url,
|
|
650
|
-
headers: { Authorization: azureAuthorization(token,
|
|
667
|
+
headers: { Authorization: azureAuthorization(token, requestOptions.isPAT) }
|
|
651
668
|
})).body;
|
|
652
669
|
} catch (e) {
|
|
653
670
|
if (isAzureNotFoundResponse(e, azureWorkItemNotFoundTypeKeys)) return void 0;
|
|
654
671
|
return this.handleProviderError(tokenWithInfo, e);
|
|
655
672
|
}
|
|
673
|
+
const project = workItem?.fields?.["System.TeamProject"];
|
|
674
|
+
if (typeof project !== "string" || !project.trim()) throw new Error(`Azure DevOps work item ${id} has no project`);
|
|
675
|
+
if (project.toLowerCase() !== scope.project.toLowerCase()) throw new Error(`Azure DevOps work item ${id} is in project '${project}', not '${scope.project}'`);
|
|
656
676
|
const issue = workItem != null ? fromAzureWorkItemToProviderIssue(workItem, scope.namespace, scope.project) : void 0;
|
|
657
677
|
if (issue == null) throw new Error(`Azure DevOps returned no readable work item ${id}`);
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
const [htmlFields] = await Promise.all([needsAzureWorkItemHtmlFields(workItem?.fields) ? this.getAzureWorkItemHtmlFields(tokenWithInfo, scope.namespace, options) : void 0, this.classifyAzureIssueStates([issue], tokenWithInfo, scope.namespace, scope.project, options)]);
|
|
661
|
-
if (htmlFields != null) Object.assign(issue, toAzureWorkItemDescriptionFields(workItem?.fields, htmlFields));
|
|
678
|
+
const [htmlFields] = await Promise.all([this.getAzureWorkItemHtmlFields(tokenWithInfo, scope, workItem?.fields, requestOptions), this.classifyAzureIssueStates([issue], tokenWithInfo, scope.namespace, scope.project, requestOptions)]);
|
|
679
|
+
Object.assign(issue, toAzureWorkItemDescriptionFields(workItem?.fields, htmlFields));
|
|
662
680
|
return issue;
|
|
663
681
|
}
|
|
664
682
|
/**
|
|
@@ -674,8 +692,11 @@ var ProvidersApi = class {
|
|
|
674
692
|
const { tokenWithInfo } = await this.ensureProviderToken(tokenOptInfo);
|
|
675
693
|
const token = tokenWithInfo.accessToken;
|
|
676
694
|
const baseUrl = (options.baseUrl ?? azureDevOpsBaseUrl).replace(/\/$/, "");
|
|
677
|
-
const
|
|
678
|
-
const
|
|
695
|
+
const encodedProject = encodeAzurePathSegment(scope.project);
|
|
696
|
+
const resource = await this.getAzureRestResource(tokenWithInfo, scope.namespace, "workItems", options);
|
|
697
|
+
const query = `ids=${ids.join(",")}&fields=${azureWorkItemEtagFieldNames.join(",")}&errorPolicy=omit&api-version=${resource.apiVersion}`;
|
|
698
|
+
const projectPath = resource.projectScoped ? `/${encodedProject}` : "";
|
|
699
|
+
const url = `${baseUrl}/${encodeAzurePathSegment(scope.namespace)}${projectPath}/_apis/wit/workitems?${query}`;
|
|
679
700
|
try {
|
|
680
701
|
const workItems = (await this.request({
|
|
681
702
|
url,
|
|
@@ -709,7 +730,11 @@ var ProvidersApi = class {
|
|
|
709
730
|
async getAzurePullRequest(tokenOptInfo, repo, id, options) {
|
|
710
731
|
const { tokenWithInfo } = await this.ensureProviderToken(tokenOptInfo);
|
|
711
732
|
const token = tokenWithInfo.accessToken;
|
|
712
|
-
const
|
|
733
|
+
const baseUrl = (options.baseUrl ?? azureDevOpsBaseUrl).replace(/\/$/, "");
|
|
734
|
+
encodeAzurePathSegment(repo.project);
|
|
735
|
+
encodeAzurePathSegment(repo.name);
|
|
736
|
+
const resource = await this.getAzureRestResource(tokenWithInfo, repo.namespace, "pullRequests", options);
|
|
737
|
+
const url = `${baseUrl}/${encodeAzurePathSegment(repo.namespace)}/${encodeAzurePathSegment(repo.project)}/_apis/git/repositories/${encodeAzurePathSegment(repo.name)}/pullrequests/${id}?api-version=${resource.apiVersion}`;
|
|
713
738
|
try {
|
|
714
739
|
const result = await this.request({
|
|
715
740
|
url,
|
|
@@ -1127,7 +1152,8 @@ var ProvidersApi = class {
|
|
|
1127
1152
|
}
|
|
1128
1153
|
async getIssuesForAzureProject(tokenOptInfo, namespace, project, options) {
|
|
1129
1154
|
const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(tokenOptInfo, "getIssuesForAzureProjectFn");
|
|
1130
|
-
const
|
|
1155
|
+
const workflow = options?.states?.includes(GitIssueState.Open) && options.states.includes(GitIssueState.Closed) ? void 0 : await this.getAzureProjectWorkItemStates(tokenWithInfo, namespace, project, options);
|
|
1156
|
+
const workItemTypeStates = workflow?.selection;
|
|
1131
1157
|
const page = await this.getPagedResult({
|
|
1132
1158
|
namespace,
|
|
1133
1159
|
project,
|
|
@@ -1146,7 +1172,7 @@ var ProvidersApi = class {
|
|
|
1146
1172
|
}
|
|
1147
1173
|
};
|
|
1148
1174
|
try {
|
|
1149
|
-
await this.classifyAzureIssueStates(result.values, tokenWithInfo, namespace, project, options,
|
|
1175
|
+
await this.classifyAzureIssueStates(result.values, tokenWithInfo, namespace, project, options, workflow?.types);
|
|
1150
1176
|
} catch (ex) {
|
|
1151
1177
|
return {
|
|
1152
1178
|
...result,
|
|
@@ -1189,22 +1215,36 @@ var ProvidersApi = class {
|
|
|
1189
1215
|
return this.handleProviderError(token, ex);
|
|
1190
1216
|
}
|
|
1191
1217
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
* rather than failing the work item. Logged for every failure, a body that isn't a catalog included.
|
|
1196
|
-
*/
|
|
1197
|
-
async getAzureWorkItemHtmlFields(token, namespace, options) {
|
|
1198
|
-
const scope = this.azureStatesScope(token, namespace, "", options);
|
|
1218
|
+
async getAzureWorkItemHtmlFields(tokenWithInfo, scope, fields, requestOptions) {
|
|
1219
|
+
const token = tokenWithInfo.accessToken;
|
|
1220
|
+
const baseUrl = requestOptions.baseUrl;
|
|
1199
1221
|
try {
|
|
1200
|
-
return await this.
|
|
1222
|
+
return await this.azureWorkItemDescriptions.getFields(fields, {
|
|
1223
|
+
baseUrl,
|
|
1224
|
+
namespace: scope.namespace,
|
|
1225
|
+
token,
|
|
1226
|
+
isPAT: requestOptions.isPAT
|
|
1227
|
+
}, async () => {
|
|
1228
|
+
const resource = await this.getAzureRestResource(tokenWithInfo, scope.namespace, "fields", requestOptions);
|
|
1229
|
+
return (await this.request({
|
|
1230
|
+
url: `${baseUrl}/${encodeAzurePathSegment(scope.namespace)}/_apis/wit/fields?api-version=${resource.apiVersion}`,
|
|
1231
|
+
headers: { Authorization: azureAuthorization(token, requestOptions.isPAT) }
|
|
1232
|
+
})).body;
|
|
1233
|
+
});
|
|
1201
1234
|
} catch (ex) {
|
|
1202
|
-
_logger.warn(`Unable to read the Azure DevOps field catalog of '${namespace}': ${String(ex)}`);
|
|
1203
|
-
return;
|
|
1235
|
+
_logger.warn(`Unable to read the Azure DevOps field catalog of '${scope.namespace}': ${String(ex)}`);
|
|
1236
|
+
return [];
|
|
1204
1237
|
}
|
|
1205
1238
|
}
|
|
1206
1239
|
getAzureTypeStates(token, scope, type) {
|
|
1207
|
-
return this.azureWorkItemTypeStates.getStates(scope, type, () =>
|
|
1240
|
+
return this.azureWorkItemTypeStates.getStates(scope, type, async () => {
|
|
1241
|
+
const resource = await this.getAzureRestResource(token, scope.namespace, "workItemTypeStates", scope);
|
|
1242
|
+
return this.readAzureStatesMetadata(token, getAzureWorkItemTypeStatesUrl(scope.baseUrl, scope.namespace, scope.project, type, resource.apiVersion), scope.isPAT);
|
|
1243
|
+
}, void 0, async () => {
|
|
1244
|
+
const resource = await this.getAzureRestResource(token, scope.namespace, "backlogConfiguration", scope);
|
|
1245
|
+
const path = `${encodeAzurePathSegment(scope.namespace)}/${encodeAzurePathSegment(scope.project)}/_apis/work/backlogconfiguration`;
|
|
1246
|
+
return this.readAzureStatesMetadata(token, `${scope.baseUrl.replace(/\/$/, "")}/${path}?api-version=${resource.apiVersion}`, scope.isPAT);
|
|
1247
|
+
});
|
|
1208
1248
|
}
|
|
1209
1249
|
async getAzureProjectWorkItemStates(token, namespace, project, options) {
|
|
1210
1250
|
const scope = this.azureStatesScope(token, namespace, project, options);
|
|
@@ -1215,45 +1255,65 @@ var ProvidersApi = class {
|
|
|
1215
1255
|
};
|
|
1216
1256
|
let types;
|
|
1217
1257
|
try {
|
|
1218
|
-
types = await this.azureWorkItemTypeStates.getTypes(scope, () =>
|
|
1258
|
+
types = await this.azureWorkItemTypeStates.getTypes(scope, async () => {
|
|
1259
|
+
const resource = await this.getAzureRestResource(token, namespace, "workItemTypes", scope);
|
|
1260
|
+
return this.readAzureStatesMetadata(token, getAzureWorkItemTypesUrl(scope.baseUrl, namespace, project, resource.apiVersion), scope.isPAT);
|
|
1261
|
+
});
|
|
1219
1262
|
} catch (ex) {
|
|
1220
1263
|
return {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1264
|
+
selection: {
|
|
1265
|
+
types: [],
|
|
1266
|
+
metadata: {
|
|
1267
|
+
completeness: "partial",
|
|
1268
|
+
failures: [toCollectionScopeFailure(failureScope, ex)]
|
|
1269
|
+
}
|
|
1270
|
+
},
|
|
1271
|
+
types: /* @__PURE__ */ new Map()
|
|
1226
1272
|
};
|
|
1227
1273
|
}
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
states
|
|
1231
|
-
|
|
1274
|
+
const statesByType = new Map(types.map((type) => [type, void 0]));
|
|
1275
|
+
const results = await mapSettledBounded(types, 6, async (type) => {
|
|
1276
|
+
const states = await this.getAzureTypeStates(token, scope, type);
|
|
1277
|
+
statesByType.set(type, states);
|
|
1278
|
+
return {
|
|
1279
|
+
name: type,
|
|
1280
|
+
states: requireAzureWorkItemStateCategories(states)
|
|
1281
|
+
};
|
|
1282
|
+
});
|
|
1232
1283
|
const failures = results.flatMap((r) => r.status === "rejected" ? [toCollectionScopeFailure(failureScope, r.reason)] : []);
|
|
1233
1284
|
return {
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1285
|
+
selection: {
|
|
1286
|
+
types: results.map((r, i) => r.status === "fulfilled" ? r.value : { name: types[i] }),
|
|
1287
|
+
...failures.length ? { metadata: {
|
|
1288
|
+
completeness: "partial",
|
|
1289
|
+
failures
|
|
1290
|
+
} } : {}
|
|
1291
|
+
},
|
|
1292
|
+
types: statesByType
|
|
1239
1293
|
};
|
|
1240
1294
|
}
|
|
1241
1295
|
async classifyAzureIssueStates(issues, tokenWithInfo, namespace, project, options, snapshot) {
|
|
1242
1296
|
const scope = this.azureStatesScope(tokenWithInfo, namespace, project, options);
|
|
1243
|
-
const snapshotTypes = new Map(snapshot
|
|
1297
|
+
const snapshotTypes = new Map(snapshot);
|
|
1244
1298
|
const types = [...new Set(issues.flatMap((i) => i.state?.name && i.type && !snapshotTypes.has(i.type) ? [i.type] : []))];
|
|
1245
1299
|
const results = await mapSettledBounded(types, 6, async (type) => [type, await this.getAzureTypeStates(tokenWithInfo, scope, type)]);
|
|
1246
1300
|
const statesByType = new Map([...snapshotTypes, ...results.flatMap((r) => r.status === "fulfilled" ? [r.value] : [])]);
|
|
1301
|
+
let categoryError;
|
|
1247
1302
|
for (const issue of issues) {
|
|
1248
1303
|
const states = issue.type ? statesByType.get(issue.type) : void 0;
|
|
1249
1304
|
if (states == null || issue.state == null) continue;
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1305
|
+
try {
|
|
1306
|
+
issue.state = {
|
|
1307
|
+
...issue.state,
|
|
1308
|
+
category: azureIssueStateCategory(resolveAzureWorkItemStateCategory(states, issue.state.name))
|
|
1309
|
+
};
|
|
1310
|
+
} catch (ex) {
|
|
1311
|
+
categoryError ??= toError(ex);
|
|
1312
|
+
}
|
|
1254
1313
|
}
|
|
1255
1314
|
const failure = results.find((r) => r.status === "rejected");
|
|
1256
1315
|
if (failure?.status === "rejected") throw failure.reason;
|
|
1316
|
+
if (categoryError != null) throw categoryError;
|
|
1257
1317
|
}
|
|
1258
1318
|
async getIssuesForProject(tokenOptInfo, project, resourceId, options) {
|
|
1259
1319
|
const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(tokenOptInfo, "getIssuesForProjectFn");
|
|
@@ -1505,49 +1565,7 @@ var ProvidersApi = class {
|
|
|
1505
1565
|
}
|
|
1506
1566
|
}
|
|
1507
1567
|
};
|
|
1508
|
-
async function parseFetchResponseForApi(response, sentCredential) {
|
|
1509
|
-
const contentType = (response.headers.get("content-type") || "").toLowerCase();
|
|
1510
|
-
if (response.status !== 401 && response.status !== 429 && response.status < 500 && getAuthenticatedUser(response.headers) === "anonymous" && sentCredential?.() === true) {
|
|
1511
|
-
response.body?.cancel().catch(() => void 0);
|
|
1512
|
-
throw new AnonymousResponseError(response.status, {
|
|
1513
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
1514
|
-
status: response.status
|
|
1515
|
-
});
|
|
1516
|
-
}
|
|
1517
|
-
let body;
|
|
1518
|
-
let servedHtml = false;
|
|
1519
|
-
if (contentType.startsWith("application/json")) {
|
|
1520
|
-
const text = await response.text();
|
|
1521
|
-
if (text.trimStart().startsWith("<")) {
|
|
1522
|
-
servedHtml = true;
|
|
1523
|
-
body = text;
|
|
1524
|
-
} else body = text.trim().length > 0 ? JSON.parse(text) : null;
|
|
1525
|
-
} else if (contentType.startsWith("text/") || contentType === "") {
|
|
1526
|
-
body = await response.text();
|
|
1527
|
-
servedHtml = contentType.startsWith("text/html") && body.trim().length > 0;
|
|
1528
|
-
} else if (contentType.startsWith("application/vnd.github.raw+json")) body = await response.arrayBuffer();
|
|
1529
|
-
else throw new Error(`Unsupported content-type: ${contentType}`);
|
|
1530
|
-
const result = {
|
|
1531
|
-
body,
|
|
1532
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
1533
|
-
status: response.status,
|
|
1534
|
-
statusText: response.statusText
|
|
1535
|
-
};
|
|
1536
|
-
if (response.ok && servedHtml) throw new UnexpectedHtmlResponseError(response.status, contentType, result);
|
|
1537
|
-
if (!response.ok) {
|
|
1538
|
-
const status = `(${response.status})${response.statusText ? ` ${response.statusText}` : ""}.`;
|
|
1539
|
-
const detail = getProviderResponseBodyMessage(body);
|
|
1540
|
-
const error = new Error(detail != null ? `${status} ${detail}` : status);
|
|
1541
|
-
Object.assign(error, { response: result });
|
|
1542
|
-
throw error;
|
|
1543
|
-
}
|
|
1544
|
-
return result;
|
|
1545
|
-
}
|
|
1546
|
-
function hasAuthorization(headers) {
|
|
1547
|
-
if (headers == null) return false;
|
|
1548
|
-
return Object.entries(headers).some(([name, value]) => name.toLowerCase() === "authorization" && Boolean(value));
|
|
1549
|
-
}
|
|
1550
1568
|
//#endregion
|
|
1551
|
-
export { ProvidersApi
|
|
1569
|
+
export { ProvidersApi };
|
|
1552
1570
|
|
|
1553
1571
|
//# sourceMappingURL=providersApi.js.map
|