diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2a891e7..4808804 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -71,6 +71,9 @@ jobs: - name: Build run: pnpm run build + - name: Lint + run: pnpm run lint + - name: Test run: pnpm run test:ci diff --git a/README.md b/README.md index b4cef1c..104fa35 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,34 @@ website, the administrators have access to an overview of the submitted projects the data at the data center, as well as have full control over the individual containers running on their systems and manage which projects/analyses have access to the various datasets. +## API Types + +Types come from two places, and the split matters. + +**FLAME Hub entities** — `Analysis`, `AnalysisNode`, `Project`, `ProjectNode`, `Node`, +`Registry`, `RegistryProject`, `MasterImage` and their approval enums — are imported directly +from `@privateaim/core-kit`, and the shared `ProcessStatus` from `@privateaim/kit`. The +hub-adapter forwards Hub entities through unchanged, so the Hub is the source of truth for +their shape, and its packages are versioned in lockstep with the API that produces them. + +**Everything the node owns** — kong, the pod-orchestrator, the event log, container logs, +node settings, health and auth — comes from `app/services/Api.ts`, generated from the +hub-adapter's OpenAPI document: + +```bash +pnpm build-api +``` + +That document also carries the adapter's own copies of the Hub entities. They are still +generated but must not be used: an ESLint `no-restricted-imports` rule fails the lint if a +Hub entity name is imported from `~/services/Api`, because those copies drift from the Hub +whenever the adapter lags behind a release. + +Hub fields are **camelCase** (`displayName`, `createdAt`, `buildStatus`) as of Hub 0.13.0. +Node-local fields are still snake_case (`created_at` on a kong route, `event_name`, +`start_date`). The two look alike and sit side by side in the same components — check which +service owns a field before renaming it. + ## Required Environment Variables This frontend requires the following environment variables to be set. The SSR framework used for this project (Nuxt) diff --git a/app/components/analysis/AnalysesTable.vue b/app/components/analysis/AnalysesTable.vue index 216c1c7..3ae5ac5 100644 --- a/app/components/analysis/AnalysesTable.vue +++ b/app/components/analysis/AnalysesTable.vue @@ -16,17 +16,21 @@ import { getApprovalStatusSeverity, getBuildStatusSeverity, getExecutionStatusSeverity, + type ApprovalStatusValue, } from "~/utils/status-tag-severity"; import { - type AnalysisNode, type AnalysisStatus, type ListRoutes, type PodProgressResponse, PodStatus, - type Project, type Route, } from "~/services/Api"; -import { ApprovalStatus } from "~/types/node"; +import { + AnalysisNodeApprovalStatus, + type AnalysisNode, + type Project, +} from "@privateaim/core-kit"; +import { ProcessStatus } from "@privateaim/kit"; import { parseKongTags } from "~/utils/parse-kong-tags"; import ContainerCounter from "~/components/analysis/ContainerCounter.vue"; import { useDatastoreRequirement } from "~/composables/useDatastoreRequirement"; @@ -34,7 +38,6 @@ import type { HubStatuses, ModifiedAnalysisNode, } from "~/services/modifiedApiInterfaces"; -import { ProcessStatus } from "~/types/analysis"; const toast = useToast(); const tableLoading = ref(true); @@ -81,20 +84,17 @@ let currentOffset = 50; // Start with query limit and will increment by same amo const kongRoutes = ref>(new Set()); // Imported values -const approvalStatuses = Object.values(ApprovalStatus); +const approvalStatuses = Object.values(AnalysisNodeApprovalStatus); const processStatuses = Object.values(ProcessStatus); const podStatuses = Object.values(PodStatus); // Combined "Hub Statuses" column filtering const HUB_STATUS_FILTER_MATCH_MODE = "hubStatusMatch"; -type HubStatusGroup = - | "approval_status" - | "build_status" - | "distribution_status"; +type HubStatusGroup = "approvalStatus" | "buildStatus" | "distributionStatus"; function getHubStatusSeverity(group: HubStatusGroup, status: string) { - return group === "approval_status" - ? getApprovalStatusSeverity(status as ApprovalStatus) + return group === "approvalStatus" + ? getApprovalStatusSeverity(status as ApprovalStatusValue) : getBuildStatusSeverity(status as ProcessStatus); } @@ -103,8 +103,8 @@ const hubStatusFilterOptions = [ label: "Approval Status", items: approvalStatuses.map((status) => ({ label: status, - value: `approval_status::${status}`, - group: "approval_status" as HubStatusGroup, + value: `approvalStatus::${status}`, + group: "approvalStatus" as HubStatusGroup, status, })), }, @@ -112,8 +112,8 @@ const hubStatusFilterOptions = [ label: "Build Status", items: processStatuses.map((status) => ({ label: status, - value: `build_status::${status}`, - group: "build_status" as HubStatusGroup, + value: `buildStatus::${status}`, + group: "buildStatus" as HubStatusGroup, status, })), }, @@ -121,8 +121,8 @@ const hubStatusFilterOptions = [ label: "Distribution Status", items: processStatuses.map((status) => ({ label: status, - value: `distribution_status::${status}`, - group: "distribution_status" as HubStatusGroup, + value: `distributionStatus::${status}`, + group: "distributionStatus" as HubStatusGroup, status, })), }, @@ -148,8 +148,8 @@ async function getProjects() { .$hubApi("/projects", { method: "GET", query: { - sort: "-updated_at", - fields: "id,name,display_name", + sort: "-updatedAt", + fields: "id,name,displayName", }, }) .catch(() => undefined)) as Project[]; @@ -187,7 +187,7 @@ async function parseProjects() { if (projData) { projData.forEach((proj: Project) => { if (proj.id) { - projMap.set(proj.id, proj.display_name ?? proj.name ?? proj.id); + projMap.set(proj.id, proj.displayName ?? proj.name ?? proj.id); } }); } @@ -219,16 +219,16 @@ async function getExecutionStatusesFromPodOrc(): Promise< function setProgress(analysis: ModifiedAnalysisNode): ModifiedAnalysisNode { // For testing: Math.round(Math.random() * 100); - analysis.execution_progress = analysis.execution_progress - ? analysis.execution_progress + analysis.executionProgress = analysis.executionProgress + ? analysis.executionProgress : 0; - const currentRunStatus = analysis.execution_status; + const currentRunStatus = analysis.executionStatus; if (currentRunStatus) { if (currentRunStatus === PodStatus.Failed) { - analysis.execution_progress = 0; + analysis.executionProgress = 0; } else if (currentRunStatus === PodStatus.Executed) { - analysis.execution_progress = 100; + analysis.executionProgress = 100; } } return analysis; @@ -256,14 +256,14 @@ function parseAnalysis( analysisEntry: ModifiedAnalysisNode, executionStatuses: PodProgressResponse | undefined, ): ModifiedAnalysisNode { - const projId = analysisEntry.analysis?.project_id; - const analysisId = analysisEntry.analysis_id; - analysisEntry.analysis_name = - analysisEntry.analysis?.display_name ?? + const projId = analysisEntry.analysis?.projectId; + const analysisId = analysisEntry.analysisId; + analysisEntry.analysisName = + analysisEntry.analysis?.displayName ?? analysisEntry.analysis?.name ?? analysisId; if (projId) { - analysisEntry.project_name = projMap.has(projId) ? projMap.get(projId) : ""; + analysisEntry.projectName = projMap.has(projId) ? projMap.get(projId) : ""; analysisEntry.datastore = kongRoutes.value.has(projId); } const acceptableHubStatuses: Array = [ @@ -272,18 +272,18 @@ function parseAnalysis( ]; if (executionStatuses && analysisId in executionStatuses) { const podStatus = executionStatuses[analysisId]!; - analysisEntry.execution_status = podStatus.status; - analysisEntry.execution_progress = - podStatus.progress ?? analysisEntry.execution_progress; + analysisEntry.executionStatus = podStatus.status; + analysisEntry.executionProgress = + podStatus.progress ?? analysisEntry.executionProgress; } else { - if (!acceptableHubStatuses.includes(analysisEntry.execution_status)) { - analysisEntry.execution_status = null; + if (!acceptableHubStatuses.includes(analysisEntry.executionStatus)) { + analysisEntry.executionStatus = null; } } - analysisEntry.hub_statuses = { - approval_status: analysisEntry.approval_status, - build_status: analysisEntry.analysis?.build_status ?? null, - distribution_status: analysisEntry.analysis?.distribution_status ?? null, + analysisEntry.hubStatuses = { + approvalStatus: analysisEntry.approvalStatus, + buildStatus: analysisEntry.analysis?.buildStatus ?? null, + distributionStatus: analysisEntry.analysis?.distributionStatus ?? null, }; return setProgress(analysisEntry); } @@ -314,13 +314,13 @@ async function compileAnalysisTable( const formattedAnalyses = formatDataRow( analysisData, - ["created_at", "updated_at"], + ["createdAt", "updatedAt"], expandRowEntries, ) as ModifiedAnalysisNode[]; if (formattedAnalyses && projMap.size > 0) { formattedAnalyses.forEach((analysisEntry: ModifiedAnalysisNode) => { parsedAnalyses.set( - analysisEntry.analysis_id, + analysisEntry.analysisId, parseAnalysis(analysisEntry, currentExecutionStatuses), ); }); @@ -385,7 +385,7 @@ async function getNextPage() { limit: queryLimit, }, include: "analysis,node", - sort: "-updated_at", + sort: "-updatedAt", }, }) .catch(() => undefined)) as AnalysisNode[] | undefined; @@ -405,8 +405,8 @@ async function getNextPage() { // Table filters const defaultFilters = { global: { value: undefined, matchMode: FilterMatchMode.CONTAINS }, - hub_statuses: { value: undefined, matchMode: HUB_STATUS_FILTER_MATCH_MODE }, - execution_status: { value: undefined, matchMode: FilterMatchMode.IN }, + hubStatuses: { value: undefined, matchMode: HUB_STATUS_FILTER_MATCH_MODE }, + executionStatus: { value: undefined, matchMode: FilterMatchMode.IN }, }; filters.value = defaultFilters; @@ -432,18 +432,18 @@ function updateAnalysisRun( if (analysesMap.value.has(analysisId)) { const analysisToUpdate = analysesMap.value.get(analysisId)!; // Tell typescript we are sure there is a value if (newStatusData) { - analysisToUpdate.execution_status = newStatusData.status; - analysisToUpdate.execution_progress = newStatusData.progress || 0; + analysisToUpdate.executionStatus = newStatusData.status; + analysisToUpdate.executionProgress = newStatusData.progress || 0; } analysesMap.value.set(analysisId, setProgress(analysisToUpdate)); } } function updateExecutionStatusFilter(filterText: string) { - const currentExecutionStatusFilters = filters.value.execution_status.value; + const currentExecutionStatusFilters = filters.value.executionStatus.value; if (!currentExecutionStatusFilters) { // If value is null then initialize with filter in array - filters.value.execution_status.value = [filterText]; + filters.value.executionStatus.value = [filterText]; } else { // Already run status filters present if (currentExecutionStatusFilters.includes(filterText)) { @@ -453,13 +453,13 @@ function updateExecutionStatusFilter(filterText: string) { ); if (filteredStatuses.length == 0) { // If empty array after filtering then set to null - filters.value.execution_status.value = undefined; + filters.value.executionStatus.value = undefined; } else { - filters.value.execution_status.value = filteredStatuses; + filters.value.executionStatus.value = filteredStatuses; } } else { // Apply filter since it isn't present - filters.value.execution_status.value.push(filterText); + filters.value.executionStatus.value.push(filterText); } } } @@ -563,7 +563,7 @@ const onCloseNavToast = () => { { dataKey="id" filterDisplay="menu" paginator - sortField="updated_at.timestamp" + sortField="updatedAt.timestamp" stripedRows tableStyle="min-width: 50rem" @page="onPage" > - + @@ -598,8 +598,8 @@ const onCloseNavToast = () => { :showClearButton="false" :showFilterMatchModes="false" :showFilterOperator="false" - field="hub_statuses" - filterField="hub_statuses" + field="hubStatuses" + filterField="hubStatuses" > - + - + @@ -822,18 +822,18 @@ const onCloseNavToast = () => { @@ -849,17 +849,17 @@ const onCloseNavToast = () => {