From c94ca49c9c6442b56a6fe850a06624b8838eafce Mon Sep 17 00:00:00 2001 From: Rob Herley Date: Sun, 3 Dec 2023 05:01:20 +0000 Subject: [PATCH] ability to filter artifacts by latest --- packages/artifact/src/internal/client.ts | 14 ++++-- .../src/internal/find/list-artifacts.ts | 50 +++++++++++++++++-- .../src/internal/shared/interfaces.ts | 9 ++++ 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/packages/artifact/src/internal/client.ts b/packages/artifact/src/internal/client.ts index 20563abc..4811104c 100644 --- a/packages/artifact/src/internal/client.ts +++ b/packages/artifact/src/internal/client.ts @@ -5,6 +5,7 @@ import { UploadResponse, DownloadArtifactOptions, GetArtifactResponse, + ListArtifactsOptions, ListArtifactsResponse, DownloadArtifactResponse, FindOptions @@ -44,7 +45,9 @@ export interface ArtifactClient { * @param options Extra options that allow for the customization of the list behavior * @returns ListArtifactResponse object */ - listArtifacts(options?: FindOptions): Promise + listArtifacts( + options?: ListArtifactsOptions & FindOptions + ): Promise /** * Finds an artifact by name. @@ -171,7 +174,9 @@ If the error persists, please check whether Actions and API requests are operati /** * List Artifacts */ - async listArtifacts(options?: FindOptions): Promise { + async listArtifacts( + options?: ListArtifactsOptions & FindOptions + ): Promise { if (isGhes()) { warning( `@actions/artifact v2.0.0+ and download-artifact@v4+ are not currently supported on GHES.` @@ -191,11 +196,12 @@ If the error persists, please check whether Actions and API requests are operati workflowRunId, repositoryOwner, repositoryName, - token + token, + options?.latest ) } - return listArtifactsInternal() + return listArtifactsInternal(options?.latest) } catch (error: unknown) { warning( `Listing Artifacts failed with error: ${error}. diff --git a/packages/artifact/src/internal/find/list-artifacts.ts b/packages/artifact/src/internal/find/list-artifacts.ts index e88c676e..a61ca583 100644 --- a/packages/artifact/src/internal/find/list-artifacts.ts +++ b/packages/artifact/src/internal/find/list-artifacts.ts @@ -20,13 +20,14 @@ export async function listArtifactsPublic( workflowRunId: number, repositoryOwner: string, repositoryName: string, - token: string + token: string, + latest = false ): Promise { info( `Fetching artifact list for workflow run ${workflowRunId} in repository ${repositoryOwner}/${repositoryName}` ) - const artifacts: Artifact[] = [] + let artifacts: Artifact[] = [] const [retryOpts, requestOpts] = getRetryOptions(defaultGitHubOptions) const opts: OctokitOptions = { @@ -100,6 +101,10 @@ export async function listArtifactsPublic( } } + if (latest) { + artifacts = filterLatest(artifacts) + } + info(`Found ${artifacts.length} artifact(s)`) return { @@ -107,7 +112,9 @@ export async function listArtifactsPublic( } } -export async function listArtifactsInternal(): Promise { +export async function listArtifactsInternal( + latest = false +): Promise { const artifactClient = internalArtifactTwirpClient() const {workflowRunBackendId, workflowJobRunBackendId} = @@ -119,7 +126,7 @@ export async function listArtifactsInternal(): Promise { } const res = await artifactClient.ListArtifacts(req) - const artifacts = res.artifacts.map(artifact => ({ + let artifacts: Artifact[] = res.artifacts.map(artifact => ({ name: artifact.name, id: Number(artifact.databaseId), size: Number(artifact.size), @@ -128,9 +135,44 @@ export async function listArtifactsInternal(): Promise { : undefined })) + if (latest) { + artifacts = filterLatest(artifacts) + } + info(`Found ${artifacts.length} artifact(s)`) return { artifacts } } + +/** + * Filters a list of artifacts to only include the latest artifact for each name + * @param artifacts The artifacts to filter + * @returns The filtered list of artifacts + */ +function filterLatest(artifacts: Artifact[]): Artifact[] { + artifacts.sort((a, b) => { + if (!a.createdAt && !b.createdAt) { + return 0 + } + if (!a.createdAt) { + return -1 + } + if (!b.createdAt) { + return 1 + } + return b.createdAt.getTime() - a.createdAt.getTime() + }) + + const latestArtifacts: Artifact[] = [] + const seenArtifactNames = new Set() + for (const artifact of artifacts) { + if (!seenArtifactNames.has(artifact.name)) { + latestArtifacts.push(artifact) + seenArtifactNames.add(artifact.name) + } + } + + return latestArtifacts +} diff --git a/packages/artifact/src/internal/shared/interfaces.ts b/packages/artifact/src/internal/shared/interfaces.ts index 0b0e3768..647e265f 100644 --- a/packages/artifact/src/internal/shared/interfaces.ts +++ b/packages/artifact/src/internal/shared/interfaces.ts @@ -74,6 +74,15 @@ export interface GetArtifactResponse { * ListArtifact * * * *****************************************************************************/ + +export interface ListArtifactsOptions { + /** + * Filter the workflow run's artifacts to the latest by name + * In the case of reruns, this can be useful to avoid duplicates + */ + latest?: boolean +} + export interface ListArtifactsResponse { /** * A list of artifacts that were found