Skip to content

Commit

Permalink
Lookout V2 UI - group by state aggregates (#2646)
Browse files Browse the repository at this point in the history
  • Loading branch information
carlocamurri committed Jul 6, 2023
1 parent 9a1ab98 commit f8c44f2
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { JobState } from "../../models/lookoutV2Models"
import { colorForJobState } from "../../utils/jobsTableFormatters"
import styles from "./JobGroupStateCounts.module.css"

interface JobGroupStateCountsProps {
stateCounts: Record<string, number>
}

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 = (
<span
className={styles.state}
key={state}
style={{
backgroundColor: color,
}}
>
{stateCounts[state as string]}
</span>
)
content.push(val)
}
return <div className={styles.container}>{content}</div>
}
28 changes: 23 additions & 5 deletions internal/lookout/ui/src/hooks/useJobsTableData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -59,6 +59,7 @@ const aggregatableFields = new Map<ColumnId, string>([
[StandardColumnId.TimeSubmittedAgo, "submitted"],
[StandardColumnId.LastTransitionTimeUtc, "lastTransitionTime"],
[StandardColumnId.TimeInState, "lastTransitionTime"],
[StandardColumnId.State, "state"],
])

export function columnIsAggregatable(columnId: ColumnId): boolean {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions internal/lookout/ui/src/models/jobsTableModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type JobGroupRow = JobRow & {
subRowCount?: number
subRows: JobTableRow[]
groupedField: string
stateCounts: Record<string, number> | undefined
}

export type JobTableRow = JobRow | JobGroupRow
Expand Down
2 changes: 1 addition & 1 deletion internal/lookout/ui/src/models/lookoutV2Models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export type JobFilter = {
export type JobGroup = {
name: string
count: number
aggregates: Record<string, string | number>
aggregates: Record<string, string | number | Record<string, number>>
}

export type SortDirection = "ASC" | "DESC"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class FakeGroupJobsService implements IGroupJobsService {
}
}

type AggregateType = "Max" | "Average"
type AggregateType = "Max" | "Average" | "State Counts"

type AggregateField = {
field: JobKey
Expand All @@ -38,6 +38,7 @@ type AggregateField = {
const aggregateFieldMap = new Map<string, AggregateField>([
["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[] {
Expand All @@ -58,22 +59,32 @@ function groupBy(jobs: Job[], groupedField: GroupedField, aggregates: string[]):
}
}
return Array.from(groups.entries()).map(([groupName, jobs]) => {
const computedAggregates: Record<string, string> = {}
const computedAggregates: Record<string, string | Record<string, number>> = {}
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<string, number> = {}
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
Expand All @@ -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<string, number> | undefined = () => undefined
if (order.field === "count") {
accessor = (group: JobGroup) => group.count
} else if (order.field === "name") {
Expand Down
20 changes: 17 additions & 3 deletions internal/lookout/ui/src/utils/jobsTableColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -204,9 +205,22 @@ export const JOB_COLUMNS: JobTableColumn[] = [
enableGrouping: true,
enableColumnFilter: true,
size: 300,
cell: (cell) => (
<JobStateLabel state={cell.getValue() as JobState}>{formatJobState(cell.getValue() as JobState)}</JobStateLabel>
),
cell: (cell) => {
if (
cell.row.original &&
isJobGroupRow(cell.row.original) &&
cell.row.original.stateCounts &&
cell.row.original.groupedField !== "state"
) {
return <JobGroupStateCounts stateCounts={cell.row.original.stateCounts} />
} else {
return (
<JobStateLabel state={cell.getValue() as JobState}>
{formatJobState(cell.getValue() as JobState)}
</JobStateLabel>
)
}
},
},
additionalMetadata: {
filterType: FilterType.Enum,
Expand Down
4 changes: 4 additions & 0 deletions internal/lookout/ui/src/utils/jobsTableUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -177,6 +178,9 @@ export const groupsToRows = (
case "lastTransitionTime":
row.lastTransitionTime = val as string
break
case "state":
row.stateCounts = val as Record<string, number>
break
default:
break
}
Expand Down

0 comments on commit f8c44f2

Please sign in to comment.