From f8c44f23c5515243496d343a046900e68f458fb7 Mon Sep 17 00:00:00 2001 From: Carlo Camurri Date: Thu, 6 Jul 2023 15:55:18 +0100 Subject: [PATCH] Lookout V2 UI - group by state aggregates (#2646) --- .../lookoutV2/JobGroupStateCounts.module.css | 14 +++++++++ .../lookoutV2/JobGroupStateCounts.tsx | 30 +++++++++++++++++++ .../lookout/ui/src/hooks/useJobsTableData.ts | 28 +++++++++++++---- .../lookout/ui/src/models/jobsTableModels.ts | 1 + .../lookout/ui/src/models/lookoutV2Models.ts | 2 +- .../lookoutV2/mocks/FakeGroupJobsService.ts | 21 +++++++++---- .../lookout/ui/src/utils/jobsTableColumns.tsx | 20 +++++++++++-- .../lookout/ui/src/utils/jobsTableUtils.ts | 4 +++ 8 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 internal/lookout/ui/src/components/lookoutV2/JobGroupStateCounts.module.css create mode 100644 internal/lookout/ui/src/components/lookoutV2/JobGroupStateCounts.tsx diff --git a/internal/lookout/ui/src/components/lookoutV2/JobGroupStateCounts.module.css b/internal/lookout/ui/src/components/lookoutV2/JobGroupStateCounts.module.css new file mode 100644 index 00000000000..fa6c99e3582 --- /dev/null +++ b/internal/lookout/ui/src/components/lookoutV2/JobGroupStateCounts.module.css @@ -0,0 +1,14 @@ +.container { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 5px; +} + +.state { + flex: 1 0 1px; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/internal/lookout/ui/src/components/lookoutV2/JobGroupStateCounts.tsx b/internal/lookout/ui/src/components/lookoutV2/JobGroupStateCounts.tsx new file mode 100644 index 00000000000..250fa1cc409 --- /dev/null +++ b/internal/lookout/ui/src/components/lookoutV2/JobGroupStateCounts.tsx @@ -0,0 +1,30 @@ +import { JobState } from "../../models/lookoutV2Models" +import { colorForJobState } from "../../utils/jobsTableFormatters" +import styles from "./JobGroupStateCounts.module.css" + +interface JobGroupStateCountsProps { + stateCounts: Record +} + +export const JobGroupStateCounts = ({ stateCounts }: JobGroupStateCountsProps) => { + const content = [] + for (const [_, state] of Object.entries(JobState)) { + if (!((state as string) in stateCounts)) { + continue + } + const color = colorForJobState(state) + const val = ( + + {stateCounts[state as string]} + + ) + content.push(val) + } + return
{content}
+} diff --git a/internal/lookout/ui/src/hooks/useJobsTableData.ts b/internal/lookout/ui/src/hooks/useJobsTableData.ts index af86754998f..4673a98c812 100644 --- a/internal/lookout/ui/src/hooks/useJobsTableData.ts +++ b/internal/lookout/ui/src/hooks/useJobsTableData.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from "react" import { ExpandedStateList, PaginationState, RowSelectionState } from "@tanstack/react-table" import { JobGroupRow, JobRow, JobTableRow } from "models/jobsTableModels" -import { Job, JobId, JobOrder, Match } from "models/lookoutV2Models" +import { Job, JobFilter, JobId, JobOrder, Match } from "models/lookoutV2Models" import { VariantType } from "notistack" import { IGetJobsService } from "services/lookoutV2/GetJobsService" import { GroupedField, IGroupJobsService } from "services/lookoutV2/GroupJobsService" @@ -59,6 +59,7 @@ const aggregatableFields = new Map([ [StandardColumnId.TimeSubmittedAgo, "submitted"], [StandardColumnId.LastTransitionTimeUtc, "lastTransitionTime"], [StandardColumnId.TimeInState, "lastTransitionTime"], + [StandardColumnId.State, "state"], ]) export function columnIsAggregatable(columnId: ColumnId): boolean { @@ -172,10 +173,7 @@ export const useFetchJobsTableData = ({ // Only relevant if we are grouping by annotations: Filter by all remaining annotations in the group by filter rowRequest.filters.push(...getFiltersForGroupedAnnotations(groupedColumns.slice(expandedLevel + 1))) - const colsToAggregate = visibleColumns - .filter((col) => aggregatableFields.has(col)) - .map((col) => aggregatableFields.get(col)) - .filter((val) => val !== undefined) as string[] + const colsToAggregate = getColsToAggregate(visibleColumns, rowRequest.filters) const { groups, count: totalGroups } = await fetchJobGroups( rowRequest, groupJobsService, @@ -258,3 +256,23 @@ const columnToGroupedField = (colId: ColumnId): GroupedField => { isAnnotation: true, } } + +const getColsToAggregate = (visibleCols: ColumnId[], filters: JobFilter[]): string[] => { + const aggregates = visibleCols + .filter((col) => aggregatableFields.has(col)) + .map((col) => aggregatableFields.get(col)) + .filter((val) => val !== undefined) as string[] + + const stateIndex = aggregates.indexOf(StandardColumnId.State) + if (stateIndex > -1) { + const stateFilter = filters.find((f) => f.field === StandardColumnId.State) + if ( + stateFilter && + ((stateFilter.match === Match.AnyOf && (stateFilter.value as string[]).length === 1) || + stateFilter.match === Match.Exact) + ) { + aggregates.splice(stateIndex, 1) + } + } + return aggregates +} diff --git a/internal/lookout/ui/src/models/jobsTableModels.ts b/internal/lookout/ui/src/models/jobsTableModels.ts index e60f90d0520..289448ef98f 100644 --- a/internal/lookout/ui/src/models/jobsTableModels.ts +++ b/internal/lookout/ui/src/models/jobsTableModels.ts @@ -13,6 +13,7 @@ export type JobGroupRow = JobRow & { subRowCount?: number subRows: JobTableRow[] groupedField: string + stateCounts: Record | undefined } export type JobTableRow = JobRow | JobGroupRow diff --git a/internal/lookout/ui/src/models/lookoutV2Models.ts b/internal/lookout/ui/src/models/lookoutV2Models.ts index dca33fb4c47..cf9bb3ad1c4 100644 --- a/internal/lookout/ui/src/models/lookoutV2Models.ts +++ b/internal/lookout/ui/src/models/lookoutV2Models.ts @@ -129,7 +129,7 @@ export type JobFilter = { export type JobGroup = { name: string count: number - aggregates: Record + aggregates: Record> } export type SortDirection = "ASC" | "DESC" diff --git a/internal/lookout/ui/src/services/lookoutV2/mocks/FakeGroupJobsService.ts b/internal/lookout/ui/src/services/lookoutV2/mocks/FakeGroupJobsService.ts index 0d2da5974c6..5fc821ac37b 100644 --- a/internal/lookout/ui/src/services/lookoutV2/mocks/FakeGroupJobsService.ts +++ b/internal/lookout/ui/src/services/lookoutV2/mocks/FakeGroupJobsService.ts @@ -28,7 +28,7 @@ export default class FakeGroupJobsService implements IGroupJobsService { } } -type AggregateType = "Max" | "Average" +type AggregateType = "Max" | "Average" | "State Counts" type AggregateField = { field: JobKey @@ -38,6 +38,7 @@ type AggregateField = { const aggregateFieldMap = new Map([ ["submitted", { field: "submitted", aggregateType: "Max" }], ["lastTransitionTime", { field: "lastTransitionTime", aggregateType: "Average" }], + ["state", { field: "state", aggregateType: "State Counts" }], ]) function groupBy(jobs: Job[], groupedField: GroupedField, aggregates: string[]): JobGroup[] { @@ -58,22 +59,32 @@ function groupBy(jobs: Job[], groupedField: GroupedField, aggregates: string[]): } } return Array.from(groups.entries()).map(([groupName, jobs]) => { - const computedAggregates: Record = {} + const computedAggregates: Record> = {} for (const aggregate of aggregates) { if (!aggregateFieldMap.has(aggregate)) { continue } const aggregateField = aggregateFieldMap.get(aggregate) as AggregateField - const values = jobs.map((job) => new Date(job[aggregateField.field] as string).getTime()) switch (aggregateField.aggregateType) { case "Max": - const max = Math.max(...values) + const max = Math.max(...jobs.map((job) => new Date(job[aggregateField.field] as string).getTime())) computedAggregates[aggregateField.field] = new Date(max).toISOString() break case "Average": + const values = jobs.map((job) => new Date(job[aggregateField.field] as string).getTime()) const avg = values.reduce((a, b) => a + b, 0) / values.length computedAggregates[aggregateField.field] = new Date(avg).toISOString() break + case "State Counts": + const stateCounts: Record = {} + for (const job of jobs) { + if (!(job.state in stateCounts)) { + stateCounts[job.state] = 0 + } + stateCounts[job.state] += 1 + } + computedAggregates[aggregateField.field] = stateCounts + break default: console.error(`aggregate type not found: ${aggregateField.aggregateType}`) break @@ -89,7 +100,7 @@ function groupBy(jobs: Job[], groupedField: GroupedField, aggregates: string[]): function comparator(order: JobOrder): (a: JobGroup, b: JobGroup) => number { return (a, b) => { - let accessor: (group: JobGroup) => string | number | undefined = () => undefined + let accessor: (group: JobGroup) => string | number | Record | undefined = () => undefined if (order.field === "count") { accessor = (group: JobGroup) => group.count } else if (order.field === "name") { diff --git a/internal/lookout/ui/src/utils/jobsTableColumns.tsx b/internal/lookout/ui/src/utils/jobsTableColumns.tsx index 452096e388b..7194bee2021 100644 --- a/internal/lookout/ui/src/utils/jobsTableColumns.tsx +++ b/internal/lookout/ui/src/utils/jobsTableColumns.tsx @@ -6,6 +6,7 @@ import { EnumFilterOption } from "components/lookoutV2/JobsTableFilter" import { isJobGroupRow, JobTableRow } from "models/jobsTableModels" import { JobState, Match } from "models/lookoutV2Models" +import { JobGroupStateCounts } from "../components/lookoutV2/JobGroupStateCounts" import { LookoutColumnOrder } from "../containers/lookoutV2/JobsTableContainer" import { formatJobState, formatTimeSince, formatUtcDate } from "./jobsTableFormatters" import { formatBytes, formatCpu, parseBytes, parseCpu, parseInteger } from "./resourceUtils" @@ -204,9 +205,22 @@ export const JOB_COLUMNS: JobTableColumn[] = [ enableGrouping: true, enableColumnFilter: true, size: 300, - cell: (cell) => ( - {formatJobState(cell.getValue() as JobState)} - ), + cell: (cell) => { + if ( + cell.row.original && + isJobGroupRow(cell.row.original) && + cell.row.original.stateCounts && + cell.row.original.groupedField !== "state" + ) { + return + } else { + return ( + + {formatJobState(cell.getValue() as JobState)} + + ) + } + }, }, additionalMetadata: { filterType: FilterType.Enum, diff --git a/internal/lookout/ui/src/utils/jobsTableUtils.ts b/internal/lookout/ui/src/utils/jobsTableUtils.ts index 69d4a8b1a01..32559acc0f5 100644 --- a/internal/lookout/ui/src/utils/jobsTableUtils.ts +++ b/internal/lookout/ui/src/utils/jobsTableUtils.ts @@ -156,6 +156,7 @@ export const groupsToRows = ( rowId: toRowId({ type: groupedField.field, value: group.name, parentRowId: baseRowId }), groupedField: groupedField.field, [groupedField.field]: group.name, + stateCounts: undefined, isGroup: true, jobCount: group.count, @@ -177,6 +178,9 @@ export const groupsToRows = ( case "lastTransitionTime": row.lastTransitionTime = val as string break + case "state": + row.stateCounts = val as Record + break default: break }