Skip to content

Unify History export UX using wizard #20666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
225 changes: 132 additions & 93 deletions client/src/components/Common/ExportRecordDetails.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import {
faCheckCircle,
faClock,
faDownload,
faExclamationCircle,
faExclamationTriangle,
faFileImport,
faHourglassEnd,
faLink,
faSpinner,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert } from "bootstrap-vue";
import { computed } from "vue";

import type { ColorVariant } from ".";
import type { ExportRecord } from "./models/exportRecordModel";
import type { ColorVariant } from "@/components/Common";
import type { CardAction, CardBadge } from "@/components/Common/GCard.types";
import type { ExportRecord } from "@/components/Common/models/exportRecordModel";

import GButton from "@/components/BaseComponents/GButton.vue";
import GCard from "@/components/Common/GCard.vue";
import Heading from "@/components/Common/Heading.vue";
import LoadingSpan from "@/components/LoadingSpan.vue";

library.add(faCheckCircle, faClock, faExclamationCircle, faExclamationTriangle, faLink);

interface Props {
record: ExportRecord;
Expand All @@ -39,118 +39,157 @@ const emit = defineEmits<{
(e: "onCopyDownloadLink", record: ExportRecord): void;
}>();

const title = computed(() => (props.record.isReady ? `Exported` : `Export started`));
const preparingMessage = computed(
() => `Preparing export. This may take some time depending on the size of your ${props.objectType}`
);

async function reimportObject() {
emit("onReimport", props.record);
}

function downloadObject() {
emit("onDownload", props.record);
}
const primaryActions = computed<CardAction[]>(() => {
const actions: CardAction[] = [];

if (props.record.canDownload) {
actions.push({
id: "copy-download-link",
label: "Copy Link",
icon: faLink,
title: "Copy the download link to clipboard",
variant: "outline-primary",
handler: () => emit("onCopyDownloadLink", props.record),
});
actions.push({
id: "download",
label: "Download",
icon: faDownload,
title: "Download the result",
variant: "primary",
handler: () => emit("onDownload", props.record),
});
}

if (props.record.canReimport) {
actions.push({
id: "reimport",
label: "Reimport",
icon: faFileImport,
title: `Reimport the ${props.objectType} from this export record`,
variant: "primary",
handler: () => emit("onReimport", props.record),
});
}

return actions;
});

function copyDownloadLink() {
emit("onCopyDownloadLink", props.record);
}
const badges = computed<CardBadge[]>(() => {
const badges: CardBadge[] = [];
if (props.record.isPreparing) {
badges.push({
id: "in-progress",
title: `${props.objectType} is being prepared for download`,
label: "In Progress",
variant: "info",
});
}
if (props.record.canDownload) {
badges.push({
id: "ready-to-download",
title: `${props.objectType} is ready to download`,
label: "Ready to Download",
variant: "success",
});
}
if (props.record.hasFailed) {
badges.push({
id: "failed",
title: `Failed to prepare ${props.objectType} for download`,
label: "Failed",
variant: "danger",
});
} else if (props.record.canExpire) {
if (props.record.hasExpired) {
badges.push({
id: "expired",
title: `The ${props.objectType} has expired and can no longer be downloaded. Please generate a new export.`,
label: "Expired",
variant: "warning",
icon: faHourglassEnd,
});
} else {
badges.push({
id: "expires-soon",
label: `Expires ${props.record.expirationElapsedTime}`,
title: `The ${props.objectType} was exported ${props.record.elapsedTime} and this download link will expire in ${props.record.expirationElapsedTime}`,
variant: "warning",
icon: faHourglassEnd,
});
}
}
return badges;
});

function onMessageDismissed() {
emit("onActionMessageDismissed");
}
</script>

<template>
<div class="export-record-details">
<Heading size="sm">
<b>{{ title }}</b> {{ props.record.elapsedTime }}
</Heading>

<p v-if="!props.record.isPreparing">
Format: <b class="record-archive-format">{{ props.record.modelStoreFormat }}</b>
</p>

<span v-if="props.record.isPreparing">
<LoadingSpan :message="preparingMessage" />
</span>
<div v-else>
<div v-if="props.record.hasFailed">
<GCard id="latest-export-record" class="export-record-details" :primary-actions="primaryActions" :badges="badges">
<template v-slot:title>
<Heading h3 size="sm">
<span v-if="props.record.isPreparing">
<FontAwesomeIcon :icon="faSpinner" spin class="mr-1" />
Preparing {{ props.objectType }} for download...
</span>
<span v-else>
This {{ props.objectType }} was exported <b>{{ props.record.elapsedTime }}</b>
</span>
</Heading>
</template>
<template v-slot:description>
<span v-if="props.record.hasFailed">
<FontAwesomeIcon
:icon="faExclamationCircle"
class="text-danger record-failed-icon"
title="Export failed" />

<span>
Something failed during this export. Please try again and if the problem persist contact your
Something failed during the export process. Please try again and if the problem persist contact your
administrator.
</span>

<BAlert show variant="danger">{{ props.record.errorMessage }}</BAlert>
</div>
<div v-else-if="props.record.isUpToDate" title="Up to date">
<BAlert :show="props.record.errorMessage" variant="danger">{{ props.record.errorMessage }}</BAlert>
</span>
<span v-if="props.record.isUpToDate" title="Up to date">
<FontAwesomeIcon :icon="faCheckCircle" class="text-success record-up-to-date-icon" />
<span> This export record contains the latest changes of the {{ props.objectType }}. </span>
</div>
<div v-else>
<span>
This export record contains the latest changes of the {{ props.objectType }} in
<b class="record-archive-format">{{ props.record.modelStoreFormat }}</b> format.
</span>
</span>
<span v-else>
<FontAwesomeIcon :icon="faExclamationTriangle" class="text-warning record-outdated-icon" />
<span>
This export is outdated and contains the changes of this {{ props.objectType }} from
{{ props.record.elapsedTime }}.
</span>
</div>
</span>

<p v-if="props.record.canExpire" class="mt-3">
<span v-if="props.record.canExpire" class="mt-3">
<span v-if="props.record.hasExpired">
<FontAwesomeIcon :icon="faClock" class="text-danger record-expired-icon" />
This download link has expired.
<FontAwesomeIcon :icon="faHourglassEnd" class="text-danger record-expired-icon" />
This download link has expired, and the result is no longer available. You can generate a new export
before downloading it again.
</span>
<span v-else>
<FontAwesomeIcon :icon="faClock" class="text-warning record-expiration-warning-icon" />
<FontAwesomeIcon :icon="faHourglassEnd" class="text-warning record-expiration-warning-icon" />
This download link expires {{ props.record.expirationElapsedTime }}.
</span>
</p>

<div v-if="props.record.isReady">
<p class="mt-3">You can do the following actions with this {{ props.objectType }} export:</p>

<BAlert
v-if="props.actionMessage !== undefined"
:variant="props.actionMessageVariant"
show
fade
dismissible
@dismissed="onMessageDismissed">
{{ props.actionMessage }}
</BAlert>
<div v-else class="actions">
<GButton
v-if="props.record.canDownload"
class="record-download-btn"
color="blue"
@click="downloadObject">
Download
</GButton>

<GButton
v-if="props.record.canDownload"
title="Copy Download Link"
size="small"
color="blue"
transparent
@click.stop="copyDownloadLink">
<FontAwesomeIcon :icon="faLink" />
</GButton>

<GButton
v-if="props.record.canReimport"
class="record-reimport-btn"
color="blue"
@click="reimportObject">
Reimport
</GButton>
</div>
</div>
</div>
</div>
</span>

<BAlert
v-if="props.actionMessage !== undefined"
:variant="props.actionMessageVariant"
show
fade
dismissible
@dismissed="onMessageDismissed">
{{ props.actionMessage }}
</BAlert>
</template>
</GCard>
</template>
Loading
Loading