- Implement release update detection, asset preloading, and frontend version management. - Add unit and E2E tests for release update workflows, widget behavior, and failure scenarios. - Introduce new services for handling release ping, error reporting, and update installation workflows. - Extend i18n for release-related components and error report localization. - Add `ReleaseFrontendVersionBadge.vue` and related styles to display frontend update statuses.
233 lines
6.0 KiB
Vue
233 lines
6.0 KiB
Vue
<script setup>
|
|
import { computed, onBeforeUnmount, onMounted, watch } from "vue";
|
|
import { releaseRuntimeState } from "@/services/releaseTimeline.js";
|
|
import {
|
|
checkReleaseUpdateNow,
|
|
downloadReleaseCandidate,
|
|
inspectReleaseRuntimeForUpdate,
|
|
installReadyReleaseUpdate,
|
|
isSameReleaseCommit,
|
|
releaseUpdateState,
|
|
shortReleaseCommit,
|
|
startReleaseUpdateAutoCheck,
|
|
} from "@/services/releaseUpdate.js";
|
|
|
|
let stopAutoCheck = null;
|
|
|
|
const updateAvailable = computed(
|
|
() =>
|
|
Boolean(releaseUpdateState.latestCommit) &&
|
|
!isSameReleaseCommit(releaseUpdateState.currentCommit, releaseUpdateState.latestCommit)
|
|
);
|
|
const isVisible = computed(
|
|
() => updateAvailable.value && ["downloading", "ready", "failed"].includes(releaseUpdateState.status)
|
|
);
|
|
const progress = computed(() => Math.max(0, Math.min(100, Number(releaseUpdateState.progress || 0))));
|
|
const progressStyle = computed(() => ({ width: `${progress.value}%` }));
|
|
const latestCommitText = computed(() => shortReleaseCommit(releaseUpdateState.latestCommit));
|
|
const currentCommitText = computed(() => shortReleaseCommit(releaseUpdateState.currentCommit));
|
|
const statusLabel = computed(() => {
|
|
if (releaseUpdateState.status === "ready") {
|
|
return "Ready";
|
|
}
|
|
if (releaseUpdateState.status === "failed") {
|
|
return "Failed";
|
|
}
|
|
return "Downloading";
|
|
});
|
|
const runtimeSignature = computed(() =>
|
|
JSON.stringify({
|
|
channel: releaseRuntimeState.channel?.slug || "",
|
|
commit: releaseRuntimeState.versions?.frontend?.commit_sha || releaseRuntimeState.versions?.frontend?.commit || "",
|
|
frontendBaseUrl: releaseRuntimeState.frontendBaseUrl || "",
|
|
configured: releaseRuntimeState.availability?.configured !== false,
|
|
})
|
|
);
|
|
const hasRuntimeCandidate = () =>
|
|
Boolean(releaseRuntimeState.channel || releaseRuntimeState.frontendBaseUrl || releaseRuntimeState.versions?.frontend);
|
|
|
|
const inspectCurrentRuntime = () => {
|
|
if (!hasRuntimeCandidate()) {
|
|
return;
|
|
}
|
|
void inspectReleaseRuntimeForUpdate(releaseRuntimeState, { autoDownload: true });
|
|
};
|
|
|
|
const retryDownload = () => {
|
|
if (releaseUpdateState.candidate) {
|
|
void downloadReleaseCandidate(releaseUpdateState.candidate);
|
|
return;
|
|
}
|
|
void checkReleaseUpdateNow();
|
|
};
|
|
|
|
const installUpdate = () => {
|
|
void installReadyReleaseUpdate();
|
|
};
|
|
|
|
onMounted(() => {
|
|
stopAutoCheck = startReleaseUpdateAutoCheck();
|
|
inspectCurrentRuntime();
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
stopAutoCheck?.();
|
|
stopAutoCheck = null;
|
|
});
|
|
|
|
watch(runtimeSignature, inspectCurrentRuntime, { immediate: true });
|
|
</script>
|
|
|
|
<template>
|
|
<section v-if="isVisible" class="release-update-widget" data-testid="release-update-widget">
|
|
<div class="release-update-widget__topline">
|
|
<span class="release-update-widget__label">{{ statusLabel }}</span>
|
|
<span class="release-update-widget__commits">{{ currentCommitText }} -> {{ latestCommitText }}</span>
|
|
</div>
|
|
|
|
<template v-if="releaseUpdateState.status === 'ready'">
|
|
<button
|
|
type="button"
|
|
class="release-update-widget__install"
|
|
data-testid="release-update-install"
|
|
@click="installUpdate"
|
|
>
|
|
Install
|
|
</button>
|
|
</template>
|
|
|
|
<template v-else-if="releaseUpdateState.status === 'failed'">
|
|
<div class="release-update-widget__failed">
|
|
<span>{{ releaseUpdateState.error || "Download failed." }}</span>
|
|
<button type="button" data-testid="release-update-retry" @click="retryDownload">Retry</button>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<div
|
|
class="release-update-widget__progress"
|
|
role="progressbar"
|
|
:aria-valuenow="progress"
|
|
aria-valuemin="0"
|
|
aria-valuemax="100"
|
|
data-testid="release-update-progress"
|
|
>
|
|
<span :style="progressStyle"></span>
|
|
</div>
|
|
<div class="release-update-widget__progress-meta">
|
|
<span>{{ progress }}%</span>
|
|
<span v-if="releaseUpdateState.totalAssets">
|
|
{{ releaseUpdateState.downloadedAssets }}/{{ releaseUpdateState.totalAssets }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.release-update-widget {
|
|
margin: -2px 16px 14px;
|
|
padding: 9px 10px;
|
|
border: 1px solid #d7e2ef;
|
|
border-radius: 5px;
|
|
background: #f8fbff;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.release-update-widget__topline,
|
|
.release-update-widget__progress-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
|
|
.release-update-widget__label {
|
|
color: #10243f;
|
|
font-size: 0.72rem;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.release-update-widget__commits {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
color: #475569;
|
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
|
font-size: 0.68rem;
|
|
font-weight: 700;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.release-update-widget__progress {
|
|
height: 7px;
|
|
margin-top: 8px;
|
|
border-radius: 999px;
|
|
background: #dbe5f0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.release-update-widget__progress span {
|
|
display: block;
|
|
height: 100%;
|
|
border-radius: inherit;
|
|
background: #153554;
|
|
transition: width 0.18s ease;
|
|
}
|
|
|
|
.release-update-widget__progress-meta {
|
|
margin-top: 5px;
|
|
color: #64748b;
|
|
font-size: 0.68rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.release-update-widget__install {
|
|
width: 100%;
|
|
min-height: 30px;
|
|
margin-top: 8px;
|
|
border: 0;
|
|
border-radius: 4px;
|
|
background: #153554;
|
|
color: #ffffff;
|
|
cursor: pointer;
|
|
font-size: 0.78rem;
|
|
font-weight: 800;
|
|
}
|
|
|
|
.release-update-widget__install:hover {
|
|
background: #10243f;
|
|
}
|
|
|
|
.release-update-widget__failed {
|
|
display: grid;
|
|
gap: 7px;
|
|
margin-top: 7px;
|
|
}
|
|
|
|
.release-update-widget__failed span {
|
|
color: #b42318;
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
line-height: 1.25;
|
|
}
|
|
|
|
.release-update-widget__failed button {
|
|
min-height: 28px;
|
|
border: 1px solid #f4a3a3;
|
|
border-radius: 4px;
|
|
background: #fff5f5;
|
|
color: #9f1c1c;
|
|
cursor: pointer;
|
|
font-size: 0.74rem;
|
|
font-weight: 800;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.release-update-widget__progress span {
|
|
transition: none;
|
|
}
|
|
}
|
|
</style>
|