feat(releases-widget): define providers as integrations (#3253)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
159
packages/integrations/src/gitlab/gitlab-integration.ts
Normal file
159
packages/integrations/src/gitlab/gitlab-integration.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import type { Gitlab as CoreGitlab } from "@gitbeaker/core";
|
||||
import { createRequesterFn, defaultOptionsHandler } from "@gitbeaker/requester-utils";
|
||||
import type { FormattedResponse, RequestOptions, ResourceOptions } from "@gitbeaker/requester-utils";
|
||||
import { Gitlab } from "@gitbeaker/rest";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
|
||||
const localLogger = logger.child({ module: "GitlabIntegration" });
|
||||
|
||||
export class GitlabIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/v4/projects"), {
|
||||
headers: {
|
||||
...(this.hasSecretValue("personalAccessToken")
|
||||
? { Authorization: `Bearer ${this.getSecretValue("personalAccessToken")}` }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return TestConnectionError.StatusResult(response);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const api = this.getApi();
|
||||
|
||||
const details = await this.getDetailsAsync(api, repository.identifier);
|
||||
|
||||
try {
|
||||
const releasesResponse = await api.ProjectReleases.all(repository.identifier, {
|
||||
perPage: 100,
|
||||
});
|
||||
|
||||
if (releasesResponse instanceof Error) {
|
||||
localLogger.warn(`Failed to get releases for ${repository.identifier} with Gitlab integration`, {
|
||||
identifier: repository.identifier,
|
||||
error: releasesResponse.message,
|
||||
});
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
}
|
||||
|
||||
const releasesProviderResponse = releasesResponse.reduce<ReleaseProviderResponse[]>((acc, release) => {
|
||||
if (!release.released_at) return acc;
|
||||
|
||||
const releaseDate = new Date(release.released_at);
|
||||
|
||||
acc.push({
|
||||
latestRelease: release.name ?? release.tag_name,
|
||||
latestReleaseAt: releaseDate,
|
||||
releaseUrl: release._links.self,
|
||||
releaseDescription: release.description ?? undefined,
|
||||
isPreRelease: releaseDate > new Date(), // For upcoming releases the `released_at` will be set to the future (https://docs.gitlab.com/api/releases/#upcoming-releases). Gitbreaker doesn't currently support the `upcoming_release` field (https://github.com/jdalrymple/gitbeaker/issues/3730)
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
} catch (error) {
|
||||
localLogger.warn(`Failed to get releases for ${repository.identifier} with Gitlab integration`, {
|
||||
identifier: repository.identifier,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected async getDetailsAsync(api: CoreGitlab, identifier: string): Promise<DetailsProviderResponse | undefined> {
|
||||
try {
|
||||
const response = await api.Projects.show(identifier);
|
||||
|
||||
if (response instanceof Error) {
|
||||
localLogger.warn(`Failed to get details for ${identifier} with Gitlab integration`, {
|
||||
identifier,
|
||||
error: response.message,
|
||||
});
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!response.web_url) {
|
||||
localLogger.warn(`No web URL found for ${identifier} with Gitlab integration`, {
|
||||
identifier,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
projectUrl: response.web_url,
|
||||
projectDescription: response.description,
|
||||
isFork: response.forked_from_project !== null,
|
||||
isArchived: response.archived,
|
||||
createdAt: new Date(response.created_at),
|
||||
starsCount: response.star_count,
|
||||
openIssues: response.open_issues_count,
|
||||
forksCount: response.forks_count,
|
||||
};
|
||||
} catch (error) {
|
||||
localLogger.warn(`Failed to get details for ${identifier} with Gitlab integration`, {
|
||||
identifier,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private getApi() {
|
||||
return new Gitlab({
|
||||
host: this.url("/").origin,
|
||||
requesterFn: createRequesterFn(
|
||||
async (serviceOptions: ResourceOptions, _: RequestOptions) => await defaultOptionsHandler(serviceOptions),
|
||||
async (endpoint: string, options?: Record<string, unknown>): Promise<FormattedResponse> => {
|
||||
if (options === undefined) {
|
||||
throw new Error("Gitlab library is not configured correctly. Options must be provided.");
|
||||
}
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(
|
||||
`${options.prefixUrl as string}${endpoint}`,
|
||||
options,
|
||||
);
|
||||
const headers = Object.fromEntries(response.headers.entries());
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
headers,
|
||||
body: await response.json(),
|
||||
} as FormattedResponse;
|
||||
},
|
||||
),
|
||||
...(this.hasSecretValue("personalAccessToken") ? { token: this.getSecretValue("personalAccessToken") } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user