Workflow Artifact Info Hover (#37100)
Add expiry metadata to action artifacts in the run view and show it on hover. --------- Signed-off-by: Nicolas <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.6) <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import {buildArtifactTooltipHtml} from './ActionRunArtifacts.ts';
|
||||
import {normalizeTestHtml} from '../utils/testhelper.ts';
|
||||
|
||||
describe('buildArtifactTooltipHtml', () => {
|
||||
test('active artifact', () => {
|
||||
const result = buildArtifactTooltipHtml({
|
||||
name: 'artifact.zip',
|
||||
size: 1024 * 1024,
|
||||
status: 'completed',
|
||||
expiresUnix: Date.UTC(2026, 2, 20, 12, 0, 0) / 1000,
|
||||
}, 'Expires at %s (extra)');
|
||||
|
||||
expect(normalizeTestHtml(result)).toBe(normalizeTestHtml(`<span class="flex-text-inline">
|
||||
<span>Expires at </span>
|
||||
<relative-time datetime="2026-03-20T12:00:00.000Z" threshold="P0Y" prefix="" weekday="" year="numeric" month="short" hour="numeric" minute="2-digit">
|
||||
2026-03-20T12:00:00.000Z
|
||||
</relative-time>
|
||||
<span> (extra)</span>
|
||||
<span class="inline-divider">,</span>
|
||||
<span>1.0 MiB</span>
|
||||
</span>
|
||||
`));
|
||||
});
|
||||
|
||||
test('no expiry', () => {
|
||||
const result = buildArtifactTooltipHtml({
|
||||
name: 'artifact.zip',
|
||||
size: 512,
|
||||
status: 'completed',
|
||||
expiresUnix: 0,
|
||||
}, 'Expires at %s');
|
||||
expect(normalizeTestHtml(result)).toBe(`<span class="flex-text-inline">512 B</span>`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import {html} from '../utils/html.ts';
|
||||
import {formatBytes} from '../utils.ts';
|
||||
import type {ActionsArtifact} from '../modules/gitea-actions.ts';
|
||||
|
||||
export function buildArtifactTooltipHtml(artifact: ActionsArtifact, expiresAtLocale: string): string {
|
||||
const sizeText = formatBytes(artifact.size);
|
||||
if (artifact.expiresUnix <= 0) {
|
||||
return html`<span class="flex-text-inline">${sizeText}</span>`; // use the same layout as below
|
||||
}
|
||||
|
||||
// split so the <relative-time> element can be interleaved, e.g. "Expires at %s" -> ["Expires at ", ""]
|
||||
const [prefix, suffix = ''] = expiresAtLocale.split('%s');
|
||||
const datetime = new Date(artifact.expiresUnix * 1000).toISOString();
|
||||
return html`
|
||||
<span class="flex-text-inline">
|
||||
<span>${prefix}</span>
|
||||
<relative-time datetime="${datetime}" threshold="P0Y" prefix="" weekday="" year="numeric" month="short" hour="numeric" minute="2-digit">
|
||||
${datetime}
|
||||
</relative-time>
|
||||
<span>${suffix}</span>
|
||||
<span class="inline-divider">,</span>
|
||||
<span>${sizeText}</span>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import {toRefs} from 'vue';
|
||||
import {POST, DELETE} from '../modules/fetch.ts';
|
||||
import ActionRunSummaryView from './ActionRunSummaryView.vue';
|
||||
import ActionRunJobView from './ActionRunJobView.vue';
|
||||
import {createActionRunViewStore} from "./ActionRunView.ts";
|
||||
import {createActionRunViewStore} from './ActionRunView.ts';
|
||||
import {buildArtifactTooltipHtml} from './ActionRunArtifacts.ts';
|
||||
|
||||
defineOptions({
|
||||
name: 'RepoActionView',
|
||||
@@ -20,7 +21,7 @@ const props = defineProps<{
|
||||
|
||||
const locale = props.locale;
|
||||
const store = createActionRunViewStore(props.actionsUrl, props.runId);
|
||||
const {currentRun: run , runArtifacts: artifacts} = toRefs(store.viewData);
|
||||
const {currentRun: run, runArtifacts: artifacts} = toRefs(store.viewData);
|
||||
|
||||
function cancelRun() {
|
||||
POST(`${run.value.link}/cancel`);
|
||||
@@ -120,18 +121,24 @@ async function deleteArtifact(name: string) {
|
||||
<ul class="ui relaxed list flex-items-block">
|
||||
<li class="item" v-for="artifact in artifacts" :key="artifact.name">
|
||||
<template v-if="artifact.status !== 'expired'">
|
||||
<a class="tw-flex-1 flex-text-block" target="_blank" :href="run.link+'/artifacts/'+artifact.name">
|
||||
<SvgIcon name="octicon-file" class="tw-text-text"/>
|
||||
<a
|
||||
class="tw-flex-1 flex-text-block muted" target="_blank"
|
||||
:href="run.link+'/artifacts/'+encodeURIComponent(artifact.name)"
|
||||
:data-tooltip-content="buildArtifactTooltipHtml(artifact, locale.artifactExpiresAt)"
|
||||
data-tooltip-render="html"
|
||||
data-tooltip-placement="top-end"
|
||||
>
|
||||
<SvgIcon name="octicon-file" class="tw-text-text-light"/>
|
||||
<span class="tw-flex-1 gt-ellipsis">{{ artifact.name }}</span>
|
||||
</a>
|
||||
<a v-if="run.canDeleteArtifact" @click="deleteArtifact(artifact.name)">
|
||||
<SvgIcon name="octicon-trash" class="tw-text-text"/>
|
||||
<a v-if="run.canDeleteArtifact" class="muted" @click="deleteArtifact(artifact.name)">
|
||||
<SvgIcon name="octicon-trash"/>
|
||||
</a>
|
||||
</template>
|
||||
<span v-else class="flex-text-block tw-flex-1 tw-text-grey-light">
|
||||
<span v-else class="flex-text-block tw-flex-1 tw-text-text-light-2">
|
||||
<SvgIcon name="octicon-file"/>
|
||||
<span class="tw-flex-1 gt-ellipsis">{{ artifact.name }}</span>
|
||||
<span class="ui label tw-text-grey-light tw-flex-shrink-0">{{ locale.artifactExpired }}</span>
|
||||
<span class="ui label tw-flex-shrink-0">{{ locale.artifactExpired }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -251,6 +258,7 @@ async function deleteArtifact(name: string) {
|
||||
|
||||
.left-list-header {
|
||||
font-size: 13px;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-light-2);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user