Skip to content
150 changes: 64 additions & 86 deletions app/components/analysis/AnalysesTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import { getAnalysisNodes } from "~/composables/useAPIFetch";
import { formatDataRow } from "~/utils/format-data-row";
import {
showCacheWarningToast,
showConnectionErrorToast
showConnectionErrorToast,
} from "~/composables/connectionErrorToast";
import { FilterMatchMode } from "@primevue/core/api";
import SearchBar from "~/components/table/SearchBar.vue";
import AnalysisControlButtons from "./AnalysisControlButtons.vue";
import {
getApprovalStatusSeverity,
getBuildStatusSeverity,
getExecutionStatusSeverity
getExecutionStatusSeverity,
} from "~/utils/status-tag-severity";
import {
type AnalysisNode,
Expand All @@ -24,7 +24,7 @@ import {
type PodProgressResponse,
PodStatus,
type Project,
type Route
type Route,
} from "~/services/Api";
import { ApprovalStatus } from "~/types/node";
import ContainerCounter from "~/components/analysis/ContainerCounter.vue";
Expand All @@ -40,12 +40,12 @@ const { nodeType, requireDataStore: datastoreRequired } =
useDatastoreRequirement();

const datastoreBadgeSeverity = computed(() =>
datastoreRequired.value ? "danger" : "secondary"
datastoreRequired.value ? "danger" : "secondary",
);
const datastoreBadgeTooltip = computed(() =>
datastoreRequired.value
? "Data store missing!"
: "Data store missing, but not required"
: "Data store missing, but not required",
);

const analysesMap = ref<Map<string, ModifiedAnalysisNode>>(new Map());
Expand All @@ -61,11 +61,11 @@ const filters = ref();
// Cache
const analysisCache = useState<AnalysisNode[] | undefined>(
"analysisCache",
() => undefined
() => undefined,
);
const projectCache = useState<Project[] | undefined>(
"projectCache",
() => undefined
() => undefined,
);
const podOrcUnreacheable = ref(false);

Expand All @@ -81,9 +81,7 @@ const approvalStatuses = Object.values(ApprovalStatus);
const processStatuses = Object.values(ProcessStatus);
const podStatuses = Object.values(PodStatus);

// Stable sort function for distribution status column — maps to binary value so
// rows group by badge (executed = '1', everything else = '0') rather than sorting
// alphabetically across the many possible status strings.
// Stable sort function for distribution status column maps to binary value
const distributionStatusSortFn = (row: ModifiedAnalysisNode) =>
row.analysis?.distribution_status === "executed" ? "1" : "0";

Expand All @@ -95,16 +93,16 @@ async function getProjects() {
method: "GET",
query: {
sort: "-updated_at",
fields: "id,name"
}
fields: "id,name",
},
})
.catch(() => undefined)) as Project[];
}

async function getKongRoutes() {
const kongRoutesResp = (await useNuxtApp()
.$hubApi("/kong/project", {
method: "GET"
method: "GET",
})
.catch(() => undefined)) as ListRoutes;
if (kongRoutesResp && kongRoutesResp.data) {
Expand Down Expand Up @@ -141,53 +139,30 @@ async function parseProjects() {
}
}

// /**
// * Changes to "executing" to "running" and "executed" to "finished" which is simpler language.
// * @param status
// */
// function useCommonLanguage(
// status: PodStatus,
// ): PodStatus | "running" | "finished" {
// if (status === PodStatus.Executing) {
// return PodStatus.Running;
// } else if (status === PodStatus.Executed) {
// return PodStatus.Finished;
// } else {
// return status;
// }
// }

async function getExecutionStatusesFromPodOrc(): Promise<
PodProgressResponse | undefined
> {
const podOrcResponse = (await useNuxtApp()
.$hubApi("/po/status", {
method: "GET"
method: "GET",
})
.catch(() => {
showConnectionErrorToast(toast, {
severity: "warn",
summary: "Missing PO Status Update",
detail:
"Unable to retrieve pod statuses from the PO, relying on information from the Hub",
life: 3000
});
if (!podOrcUnreacheable.value) {
podOrcUnreacheable.value = true;
showConnectionErrorToast(toast, {
severity: "warn",
summary: "Missing PO Status Update",
detail:
"Unable to retrieve pod statuses from the PO, relying on information from the Hub",
life: 3000,
});
}
return undefined;
})) as PodProgressResponse;
podOrcUnreacheable.value = !podOrcResponse;
return podOrcResponse;
}

async function checkForUpdatesFromPodOrc() {
if (!podOrcUnreacheable.value) {
const newStatuses = await getExecutionStatusesFromPodOrc();
if (newStatuses) {
for (const [analysisId, progressData] of Object.entries(newStatuses)) {
updateAnalysisRun(analysisId, progressData);
}
}
}
}

function setProgress(analysis: ModifiedAnalysisNode): ModifiedAnalysisNode {
// For testing: Math.round(Math.random() * 100);
analysis.execution_progress = analysis.execution_progress
Expand All @@ -198,10 +173,7 @@ function setProgress(analysis: ModifiedAnalysisNode): ModifiedAnalysisNode {
if (currentRunStatus) {
if (currentRunStatus === PodStatus.Failed) {
analysis.execution_progress = 0;
} else if (
currentRunStatus === PodStatus.Executed ||
currentRunStatus === PodStatus.Finished // deprecated
) {
} else if (currentRunStatus === PodStatus.Executed) {
analysis.execution_progress = 100;
}
}
Expand All @@ -222,28 +194,23 @@ function determineProgressBarColor(progress: number) {
}

return {
"--p-progressbar-value-background": color
"--p-progressbar-value-background": color,
};
}

function parseAnalysis(
analysisEntry: ModifiedAnalysisNode,
executionStatuses: PodProgressResponse | undefined
executionStatuses: PodProgressResponse | undefined,
): ModifiedAnalysisNode {
const projId = analysisEntry.analysis?.project_id;
const analysisId = analysisEntry.analysis_id;
if (projId) {
analysisEntry.project_name = projMap.has(projId) ? projMap.get(projId) : "";
analysisEntry.datastore = kongRoutes.value.has(projId);
}
// If PodOrc status update returns undefined -> use hub info since it's all we have
// If status from PodOrc -> use it
// If no run status reported by PodOrc, and it's not failed/finished -> set to null (wrong hub info)
// REMEMBER: PO sends running/finished instead of executing/executed
const acceptableHubStatuses: Array<PodStatus | null | undefined> = [
PodStatus.Failed,
PodStatus.Executed,
PodStatus.Finished // Deprecated but still returned by PO
];
if (executionStatuses && analysisId in executionStatuses) {
const podStatus = executionStatuses[analysisId]!;
Expand All @@ -260,9 +227,10 @@ function parseAnalysis(

async function compileAnalysisTable(
respStatus: string,
respData: AnalysisNode[] | undefined
respData: AnalysisNode[] | undefined,
silent = false,
) {
tableLoading.value = true;
if (!silent) tableLoading.value = true;
await parseProjects();
await getKongRoutes();
const parsedAnalyses = new Map<string, ModifiedAnalysisNode>();
Expand All @@ -281,31 +249,41 @@ async function compileAnalysisTable(
const formattedAnalyses = formatDataRow(
analysisData,
["created_at", "updated_at"],
expandRowEntries
expandRowEntries,
) as ModifiedAnalysisNode[];
if (formattedAnalyses && projMap.size > 0) {
formattedAnalyses.forEach((analysisEntry: ModifiedAnalysisNode) => {
parsedAnalyses.set(
analysisEntry.analysis_id,
parseAnalysis(analysisEntry, currentExecutionStatuses)
parseAnalysis(analysisEntry, currentExecutionStatuses),
);
});
analysesMap.value = parsedAnalyses;
}
tableLoading.value = false;
}

let pollIntervalId: ReturnType<typeof setInterval> | undefined;
let tableRefreshIntervalId: ReturnType<typeof setInterval> | undefined;
let isPolling = false;

async function pollTableData() {
if (isPolling) return;
isPolling = true;
try {
await refresh();
await compileAnalysisTable(status.value, analysisNodeResp.value, true);
} finally {
isPolling = false;
}
}
Comment thread
brucetony marked this conversation as resolved.

onMounted(() => {
compileAnalysisTable(status.value, analysisNodeResp.value);
pollIntervalId = setInterval(checkForUpdatesFromPodOrc, 15000); // Poll PO every 15 seconds
tableRefreshIntervalId = setInterval(pollTableData, 15000);
});

onUnmounted(() => {
if (pollIntervalId) {
clearInterval(pollIntervalId);
}
if (tableRefreshIntervalId) clearInterval(tableRefreshIntervalId);
});

async function onTableRefresh() {
Expand Down Expand Up @@ -334,11 +312,11 @@ async function getNextPage() {
query: {
page: {
offset: currentOffset,
limit: queryLimit
limit: queryLimit,
},
include: "analysis,node",
sort: "-updated_at"
}
sort: "-updated_at",
},
})
.catch(() => undefined)) as AnalysisNode[];
if (nextSetResults.length > 0) {
Expand All @@ -359,15 +337,15 @@ const defaultFilters = {
global: { value: undefined, matchMode: FilterMatchMode.CONTAINS },
approval_status: { value: undefined, matchMode: FilterMatchMode.EQUALS },
"analysis.build_status": { value: undefined, matchMode: FilterMatchMode.IN },
execution_status: { value: undefined, matchMode: FilterMatchMode.IN }
execution_status: { value: undefined, matchMode: FilterMatchMode.IN },
};
filters.value = defaultFilters;

function resetFilters() {
const clearedFilters = {};
for (const filterKey in defaultFilters) {
clearedFilters[filterKey] = {
...defaultFilters[filterKey]
...defaultFilters[filterKey],
};
clearedFilters[filterKey].value = undefined;
}
Expand All @@ -380,7 +358,7 @@ const updateFilters = (filterText: string) => {

function updateAnalysisRun(
analysisId: string,
newStatusData: AnalysisStatus | undefined
newStatusData: AnalysisStatus | undefined,
) {
if (analysesMap.value.has(analysisId)) {
const analysisToUpdate = analysesMap.value.get(analysisId)!; // Tell typescript we are sure there is a value
Expand All @@ -402,7 +380,7 @@ function updateExecutionStatusFilter(filterText: string) {
if (currentExecutionStatusFilters.includes(filterText)) {
// If filter already there, then remove it
const filteredStatuses = currentExecutionStatusFilters.filter(
(item) => item !== filterText
(item) => item !== filterText,
);
if (filteredStatuses.length == 0) {
// If empty array after filtering then set to null
Expand All @@ -425,7 +403,7 @@ const showDataStoreNavToast = () => {
"Unable to find an associated data store, click the button below " +
"to create a data store for the project of this analysis",
group: "datastoreToastLink",
life: 10000
life: 10000,
});
};

Expand Down Expand Up @@ -659,15 +637,15 @@ const onCloseNavToast = () => {
class="distribution-badge"
>
<Badge class="w-8 h-8 rounded-full" severity="success"
><i v-tooltip.top="'Image available'" class="pi pi-check"></i
><i v-tooltip.top="'Image available'" class="pi pi-check"></i
></Badge>
</div>
<div v-else class="distribution-badge">
<Badge class="w-8 h-8 rounded-full" :severity="'danger'"
><i
v-tooltip.top="'Image unavailable'"
class="pi pi-times"
></i
><i
v-tooltip.top="'Image unavailable'"
class="pi pi-times"
></i
></Badge>
</div>
</template>
Expand Down Expand Up @@ -749,17 +727,17 @@ const onCloseNavToast = () => {
<template #body="{ data }">
<div v-if="data.datastore" class="datastore-badge">
<Badge class="w-8 h-8 rounded-full" severity="success"
><i v-tooltip.top="'Data store found'" class="pi pi-check"></i
><i v-tooltip.top="'Data store found'" class="pi pi-check"></i
></Badge>
</div>
<div v-else class="datastore-badge">
<Badge
class="w-8 h-8 rounded-full"
:severity="datastoreBadgeSeverity"
><i
v-tooltip.top="datastoreBadgeTooltip"
class="pi pi-times"
></i
><i
v-tooltip.top="datastoreBadgeTooltip"
class="pi pi-times"
></i
></Badge>
</div>
</template>
Expand Down
1 change: 1 addition & 0 deletions app/components/analysis/AnalysisControlButtons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ async function onDeleteAnalysis() {
:to="{
name: 'analyses-id',
params: { id: props.analysisId },
query: { nodeId: props.analysisNodeId },
}"
target="_blank"
>
Expand Down
Loading
Loading