Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5d9a44e
Add artifact expiry feature with tooltip and localization support
bircni Apr 3, 2026
7759b8d
fixes
bircni Apr 4, 2026
96d62cf
Merge branch 'main' into feature/add-artifact-expiry-hover
bircni Apr 4, 2026
415044d
Merge branch 'main' into feature/add-artifact-expiry-hover
silverwind Apr 9, 2026
ecc8429
Refactor artifact tooltip and styling
silverwind Apr 9, 2026
06ba537
Improve artifact link click area and tooltip spacing
silverwind Apr 9, 2026
53a4c79
Move formatBytes to utils.ts, improve artifact click area
silverwind Apr 9, 2026
83f0d17
Encode artifact name in download URL, use 2-digit minutes
silverwind Apr 9, 2026
322023f
Update devtest relative-time to use minute=2-digit
silverwind Apr 9, 2026
3887aaf
Use Tailwind border separator in tooltip, fix whitespace
silverwind Apr 9, 2026
840ca21
Simplify formatBytes
silverwind Apr 9, 2026
6cc7c34
Merge branch 'main' into feature/add-artifact-expiry-hover
silverwind Apr 9, 2026
65ac91d
Rename formatBytes to formatNumber with configurable suffix and preci…
silverwind Apr 9, 2026
f822379
Refactor formatBytes to use log-based unit calculation
silverwind Apr 9, 2026
cac82af
Use data-tooltip-* attributes for artifact tooltip
silverwind Apr 18, 2026
404f0f9
Merge branch 'main' into feature/add-artifact-expiry-hover
bircni Apr 18, 2026
1e42ce6
fix
wxiaoguang Apr 19, 2026
45c1693
fix test
wxiaoguang Apr 19, 2026
7d7a2aa
cover one more edge case
wxiaoguang Apr 19, 2026
33c16a8
Merge branch 'main' into feature/add-artifact-expiry-hover
bircni Apr 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion models/actions/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ type ActionArtifactMeta struct {
ArtifactName string
FileSize int64
Status ArtifactStatus
ExpiredUnix timeutil.TimeStamp
}

// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
Expand All @@ -191,7 +192,7 @@ func ListUploadedArtifactsMeta(ctx context.Context, repoID, runID int64) ([]*Act
return arts, db.GetEngine(ctx).Table("action_artifact").
Where("repo_id=? AND run_id=? AND (status=? OR status=?)", repoID, runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired).
GroupBy("artifact_name").
Select("artifact_name, sum(file_size) as file_size, max(status) as status").
Select("artifact_name, sum(file_size) as file_size, max(status) as status, max(expired_unix) as expired_unix").
Find(&arts)
}

Expand Down
1 change: 1 addition & 0 deletions options/locale/locale_en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
"unpin": "Unpin",
"artifacts": "Artifacts",
"expired": "Expired",
"artifact_expires_at": "Expires at",
Comment thread
bircni marked this conversation as resolved.
Outdated
"confirm_delete_artifact": "Are you sure you want to delete the artifact '%s'?",
"archived": "Archived",
"concept_system_global": "Global",
Expand Down
28 changes: 16 additions & 12 deletions routers/web/devtest/mock_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,24 +96,28 @@ func MockActionsRunsJobs(ctx *context.Context) {
},
}
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-a",
Size: 100 * 1024,
Status: "expired",
Name: "artifact-a",
Size: 100 * 1024,
Status: "expired",
ExpiresUnix: time.Now().Add(-24 * time.Hour).Unix(),
})
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-b",
Size: 1024 * 1024,
Status: "completed",
Name: "artifact-b",
Size: 1024 * 1024,
Status: "completed",
ExpiresUnix: time.Now().Add(24 * time.Hour).Unix(),
})
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-very-loooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
Size: 100 * 1024,
Status: "expired",
Name: "artifact-very-loooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
Size: 100 * 1024,
Status: "expired",
ExpiresUnix: time.Now().Add(-24 * time.Hour).Unix(),
})
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-really-loooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
Size: 1024 * 1024,
Status: "completed",
Name: "artifact-really-loooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
Size: 1024 * 1024,
Status: "completed",
ExpiresUnix: time.Now().Add(24 * time.Hour).Unix(),
})

resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
Expand Down
14 changes: 8 additions & 6 deletions routers/web/repo/actions/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,10 @@ type ViewRequest struct {
}

type ArtifactsViewItem struct {
Name string `json:"name"`
Size int64 `json:"size"`
Status string `json:"status"`
Name string `json:"name"`
Size int64 `json:"size"`
Status string `json:"status"`
ExpiresUnix int64 `json:"expiresUnix"`
}

type ViewResponse struct {
Expand Down Expand Up @@ -344,9 +345,10 @@ func getActionsViewArtifacts(ctx context.Context, repoID, runID int64) (artifact
}
for _, art := range artifacts {
artifactsViewItems = append(artifactsViewItems, &ArtifactsViewItem{
Name: art.ArtifactName,
Size: art.FileSize,
Status: util.Iif(art.Status == actions_model.ArtifactStatusExpired, "expired", "completed"),
Name: art.ArtifactName,
Size: art.FileSize,
Status: util.Iif(art.Status == actions_model.ArtifactStatusExpired, "expired", "completed"),
ExpiresUnix: int64(art.ExpiredUnix),
})
}
return artifactsViewItems, nil
Expand Down
2 changes: 2 additions & 0 deletions templates/repo/actions/view_component.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
data-locale-status-blocked="{{ctx.Locale.Tr "actions.status.blocked"}}"
data-locale-artifacts-title="{{ctx.Locale.Tr "artifacts"}}"
data-locale-artifact-expired="{{ctx.Locale.Tr "expired"}}"
data-locale-artifact-expires-at="{{ctx.Locale.Tr "artifact_expires_at"}}"
data-locale-artifact-size="{{ctx.Locale.Tr "repo.diff.file_byte_size"}}"
data-locale-confirm-delete-artifact="{{ctx.Locale.Tr "confirm_delete_artifact"}}"
data-locale-show-timestamps="{{ctx.Locale.Tr "show_timestamps"}}"
data-locale-show-log-seconds="{{ctx.Locale.Tr "show_log_seconds"}}"
Expand Down
38 changes: 38 additions & 0 deletions web_src/js/components/ActionRunArtifacts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {createArtifactTooltipContent, formatArtifactSize, formatArtifactTimestamp} from './ActionRunArtifacts.ts';

test('createArtifactTooltipContent', () => {
document.documentElement.lang = 'en-US';
const locale = {
artifactExpired: 'Expired',
artifactExpiresAt: 'Expires at',
artifactSize: 'Size',
status: {
unknown: 'Unknown',
},
};

expect(createArtifactTooltipContent({
name: 'artifact.zip',
size: 1536,
status: 'completed',
expiresUnix: Date.UTC(2026, 2, 20, 12, 0, 0) / 1000,
}, locale)).toContain('Expires at:');

expect(createArtifactTooltipContent({
name: 'artifact.zip',
size: 0,
status: 'expired',
expiresUnix: 0,
}, locale)).toBe('Expired | Size: 0 B');
});

test('formatArtifactTimestamp', () => {
document.documentElement.lang = 'en-US';
expect(formatArtifactTimestamp(0)).toBeNull();
expect(formatArtifactTimestamp(Number.NaN)).toBeNull();
});

test('formatArtifactSize', () => {
expect(formatArtifactSize(0)).toBe('0 B');
expect(formatArtifactSize(1536)).toBe('1.5 KiB');
});
44 changes: 44 additions & 0 deletions web_src/js/components/ActionRunArtifacts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type {ActionsArtifact} from '../modules/gitea-actions.ts';
import {formatDatetime} from '../utils/time.ts';

export type ArtifactTooltipLocale = {
artifactExpired: string;
artifactExpiresAt: string;
artifactSize: string;
status: {
unknown: string;
};
};

const sizeUnits = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];

export function formatArtifactSize(size: number): string {
let value = size;
let unitIndex = 0;

while (value >= 1024 && unitIndex < sizeUnits.length - 1) {
value /= 1024;
unitIndex++;
}

const formattedValue = unitIndex === 0 ? String(Math.round(value)) : value.toFixed(value >= 10 ? 0 : 1);
return `${formattedValue} ${sizeUnits[unitIndex]}`;
}

export function formatArtifactTimestamp(expiresUnix: number): string | null {
if (expiresUnix === null || !Number.isFinite(expiresUnix) || expiresUnix <= 0) return null;
return formatDatetime(new Date(expiresUnix * 1000));
}

export function createArtifactTooltipContent(artifact: ActionsArtifact, locale: ArtifactTooltipLocale): string {
const details = [];

if (artifact.status === 'expired') {
details.push(locale.artifactExpired);
} else {
details.push(`${locale.artifactExpiresAt}: ${formatArtifactTimestamp(artifact.expiresUnix) ?? locale.status.unknown}`);
}
details.push(`${locale.artifactSize}: ${formatArtifactSize(artifact.size)}`);

return details.join(' | ');
}
13 changes: 9 additions & 4 deletions web_src/js/components/RepoActionView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 {createArtifactTooltipContent, type ArtifactTooltipLocale} from './ActionRunArtifacts.ts';

defineOptions({
name: 'RepoActionView',
Expand All @@ -20,7 +21,11 @@ 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 artifactTooltip(artifact: typeof artifacts.value[number]) {
return createArtifactTooltipContent(artifact, locale as ArtifactTooltipLocale);
}

function cancelRun() {
POST(`${run.value.link}/cancel`);
Expand Down Expand Up @@ -120,15 +125,15 @@ 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">
<a class="tw-flex-1 flex-text-block" target="_blank" :href="run.link+'/artifacts/'+artifact.name" :data-tooltip-content="artifactTooltip(artifact)">
<SvgIcon name="octicon-file" class="tw-text-text"/>
<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>
Comment thread
bircni marked this conversation as resolved.
</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-grey-light" :data-tooltip-content="artifactTooltip(artifact)">
<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>
Expand Down
2 changes: 2 additions & 0 deletions web_src/js/features/repo-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export function initRepositoryActionView() {
artifactsTitle: el.getAttribute('data-locale-artifacts-title'),
areYouSure: el.getAttribute('data-locale-are-you-sure'),
artifactExpired: el.getAttribute('data-locale-artifact-expired'),
artifactExpiresAt: el.getAttribute('data-locale-artifact-expires-at'),
artifactSize: el.getAttribute('data-locale-artifact-size'),
confirmDeleteArtifact: el.getAttribute('data-locale-confirm-delete-artifact'),
showTimeStamps: el.getAttribute('data-locale-show-timestamps'),
showLogSeconds: el.getAttribute('data-locale-show-log-seconds'),
Expand Down
2 changes: 2 additions & 0 deletions web_src/js/modules/gitea-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,7 @@ export type ActionsJob = {

export type ActionsArtifact = {
name: string;
size: number;
status: string;
expiresUnix: number;
};