Status
@@ -213,53 +250,93 @@ ReleasePage.PublishStatus = (props: {
- {!release.scheduledAt && (
-
-
-
+ onArchiveClicked()}
+ loading={archiveLoading}
+ >
+ Archive
+
+
+ >
)}
- {release.scheduledAt ? (
-
- onCancelScheduleClicked()}
- >
- Cancel Schedule
-
-
- ) : (
+ {release.archivedAt && (
onScheduleClicked()}
+ onClick={() => onUnarchiveClicked()}
+ loading={archiveLoading}
>
- Schedule
+ Unarchive
)}
diff --git a/packages/root-cms/ui/pages/ReleasesPage/ReleasesPage.css b/packages/root-cms/ui/pages/ReleasesPage/ReleasesPage.css
index f85c6708c..fde60e7d9 100644
--- a/packages/root-cms/ui/pages/ReleasesPage/ReleasesPage.css
+++ b/packages/root-cms/ui/pages/ReleasesPage/ReleasesPage.css
@@ -28,3 +28,11 @@
.ReleasesPage__ReleasesTable__publishStatus__icon {
color: #12b886;
}
+
+.ReleasesPage__ReleasesTable__controls {
+ margin-top: 16px;
+}
+
+.ReleasesPage__row--archived {
+ opacity: 0.6;
+}
diff --git a/packages/root-cms/ui/pages/ReleasesPage/ReleasesPage.tsx b/packages/root-cms/ui/pages/ReleasesPage/ReleasesPage.tsx
index ed2d111f1..5b40242f6 100644
--- a/packages/root-cms/ui/pages/ReleasesPage/ReleasesPage.tsx
+++ b/packages/root-cms/ui/pages/ReleasesPage/ReleasesPage.tsx
@@ -1,4 +1,4 @@
-import {Button, Loader, Table} from '@mantine/core';
+import {Button, Loader, Switch, Table} from '@mantine/core';
import {useEffect, useState} from 'preact/hooks';
import {Heading} from '../../components/Heading/Heading.js';
import {ReleaseStatusBadge} from '../../components/ReleaseStatusBadge/ReleaseStatusBadge.js';
@@ -36,16 +36,18 @@ export function ReleasesPage() {
ReleasesPage.ReleasesTable = () => {
const [loading, setLoading] = useState(true);
const [tableData, setTableData] = useState ([]);
+ const [includeArchived, setIncludeArchived] = useState(false);
async function init() {
- const releases = await listReleases();
+ setLoading(true);
+ const releases = await listReleases({includeArchived});
setTableData(releases);
setLoading(false);
}
useEffect(() => {
init();
- }, []);
+ }, [includeArchived]);
return (
@@ -62,7 +64,10 @@ ReleasesPage.ReleasesTable = () => {
{tableData.map((release) => (
-
+
|
{release.id}
|
@@ -84,6 +89,14 @@ ReleasesPage.ReleasesTable = () => {
)}
+
+ setIncludeArchived(event.currentTarget.checked)}
+ label="Show archived"
+ />
+
);
};
diff --git a/packages/root-cms/ui/utils/release.ts b/packages/root-cms/ui/utils/release.ts
index 1abdd25c5..2e0b96eaa 100644
--- a/packages/root-cms/ui/utils/release.ts
+++ b/packages/root-cms/ui/utils/release.ts
@@ -28,6 +28,8 @@ export interface Release {
scheduledBy?: string;
publishedAt?: Timestamp;
publishedBy?: string;
+ archivedAt?: Timestamp;
+ archivedBy?: string;
}
const COLLECTION_ID = 'Releases';
@@ -54,7 +56,9 @@ export async function addRelease(id: string, release: Partial) {
logAction('release.create', {metadata: {releaseId: id}});
}
-export async function listReleases(): Promise {
+export async function listReleases(
+ options: {includeArchived?: boolean} = {}
+): Promise {
const projectId = window.__ROOT_CTX.rootConfig.projectId;
const db = window.firebase.db;
const colRef = collection(db, 'Projects', projectId, COLLECTION_ID);
@@ -64,7 +68,10 @@ export async function listReleases(): Promise {
querySnapshot.forEach((doc) => {
res.push(doc.data() as Release);
});
- return res;
+ if (options.includeArchived) {
+ return res;
+ }
+ return res.filter((release) => !release.archivedAt);
}
export async function getRelease(id: string) {
@@ -100,6 +107,9 @@ export async function publishRelease(id: string) {
if (!release) {
throw new Error(`release not found: ${id}`);
}
+ if (release.archivedAt) {
+ throw new Error(`release is archived: ${id}`);
+ }
const docIds = release.docIds || [];
const dataSourceIds = release.dataSourceIds || [];
if (docIds.length === 0 && dataSourceIds.length === 0) {
@@ -138,6 +148,9 @@ export async function scheduleRelease(
if (!release) {
throw new Error(`release not found: ${id}`);
}
+ if (release.archivedAt) {
+ throw new Error(`release is archived: ${id}`);
+ }
if (typeof timestamp === 'number') {
timestamp = Timestamp.fromMillis(timestamp);
@@ -172,3 +185,39 @@ export async function cancelScheduledRelease(id: string) {
});
logAction('release.unschedule', {metadata: {releaseId: id}});
}
+
+export async function archiveRelease(id: string) {
+ const release = await getRelease(id);
+ if (!release) {
+ throw new Error(`release not found: ${id}`);
+ }
+
+ const projectId = window.__ROOT_CTX.rootConfig.projectId;
+ const db = window.firebase.db;
+ const docRef = doc(db, 'Projects', projectId, COLLECTION_ID, id);
+
+ await updateDoc(docRef, {
+ archivedAt: serverTimestamp(),
+ archivedBy: window.firebase.user.email,
+ scheduledAt: deleteField(),
+ scheduledBy: deleteField(),
+ });
+ logAction('release.archive', {metadata: {releaseId: id}});
+}
+
+export async function unarchiveRelease(id: string) {
+ const release = await getRelease(id);
+ if (!release) {
+ throw new Error(`release not found: ${id}`);
+ }
+
+ const projectId = window.__ROOT_CTX.rootConfig.projectId;
+ const db = window.firebase.db;
+ const docRef = doc(db, 'Projects', projectId, COLLECTION_ID, id);
+
+ await updateDoc(docRef, {
+ archivedAt: deleteField(),
+ archivedBy: deleteField(),
+ });
+ logAction('release.unarchive', {metadata: {releaseId: id}});
+}
|