From a43cd364898419f62c0688a86c1d613ef8e046cf Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Mon, 17 Mar 2025 15:33:13 -0700 Subject: [PATCH 01/12] tc --- .../MainPageSettings.module.css | 40 +++ .../HudGroupingSettings/MainPageSettings.tsx | 281 ++++++++++++++++++ .../HudGroupingSettings/defaults.ts | 174 +++++++++++ .../hudGroupingSettings.ts | 129 ++++++++ .../mainPageSettingsUtils.ts | 50 ++++ .../components/common/ValidatedTextField.tsx | 33 ++ torchci/lib/JobClassifierUtil.ts | 51 +--- torchci/package.json | 4 + .../[repoName]/[branch]/[[...page]].tsx | 14 +- torchci/yarn.lock | 54 +++- 10 files changed, 770 insertions(+), 60 deletions(-) create mode 100644 torchci/components/HudGroupingSettings/MainPageSettings.module.css create mode 100644 torchci/components/HudGroupingSettings/MainPageSettings.tsx create mode 100644 torchci/components/HudGroupingSettings/defaults.ts create mode 100644 torchci/components/HudGroupingSettings/hudGroupingSettings.ts create mode 100644 torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts create mode 100644 torchci/components/common/ValidatedTextField.tsx diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.module.css b/torchci/components/HudGroupingSettings/MainPageSettings.module.css new file mode 100644 index 0000000000..5735c77145 --- /dev/null +++ b/torchci/components/HudGroupingSettings/MainPageSettings.module.css @@ -0,0 +1,40 @@ +.draggingSource { + opacity: 0.3; +} + +.dropTarget { + background-color: #e8f0fe; +} + +.expandIconWrapper.isOpen { + transform: rotate(90deg); +} + +.expandIconWrapper { + align-items: center; + font-size: 0; + cursor: pointer; + display: flex; + justify-content: center; + transform: rotate(0deg); + margin: 0; + padding: 0; +} + +.node { + height: 32px; + padding-inline-end: 8px; +} +.root { + list-style: none; +} +.root ul { + list-style: none; +} +.labelGridItem { + padding-inline-start: 8px; +} + +.root li:hover { + background-color: #f0f0f0; +} diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.tsx b/torchci/components/HudGroupingSettings/MainPageSettings.tsx new file mode 100644 index 0000000000..6e6c2ce6fb --- /dev/null +++ b/torchci/components/HudGroupingSettings/MainPageSettings.tsx @@ -0,0 +1,281 @@ +import { KeyboardArrowDown } from "@mui/icons-material"; +import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + List, + ListItem, + ListItemButton, + Stack, + Typography, +} from "@mui/material"; +import { ValidatedTextField } from "components/common/ValidatedTextField"; +import { getDefaultGroupSettings } from "components/HudGroupingSettings/defaults"; +import * as React from "react"; +import { useState } from "react"; +import { + getNonDupNewName, + getStoredTreeData, + Group, + isDupName, + saveTreeData, +} from "./mainPageSettingsUtils"; + +function validRegex(value: string) { + try { + new RegExp(value); + return true; + } catch (e) { + return false; + } +} + +function EditSectionDialog({ + treeData, + name, + setGroup, +}: { + treeData: Group[]; + name: string; + setGroup: (name: string, newName: string, regex: string) => void; +}) { + const [open, setOpen] = useState(false); + + function isGoodName(value: string) { + return value == name || !isDupName(treeData, value); + } + + return ( + <> + + setOpen(false)} + aria-modal + > + + { + e.preventDefault(); + // @ts-ignore + const regex = e.target[2].value; + // @ts-ignore + const newName = e.target[0].value; + if (!validRegex(regex) || !isGoodName(newName)) { + return; + } + setGroup(name, newName, regex); + setOpen(false); + }} + > + + node.name === name)?.regex.source ?? "" + } + errorMessage="Invalid regex" + /> + + + + + + + + ); +} + +export default function SettingsModal({ + visible, + handleClose, +}: { + visible: boolean; + handleClose: () => void; +}) { + const [treeData, setTreeData] = useState(getStoredTreeData()); + const [orderBy, setOrderBy] = useState<"display" | "filter">("display"); + + function addSection() { + setTreeData([ + { + regex: new RegExp(""), + name: getNonDupNewName(treeData), + filterPriority: 0, + displayPriority: 0, + persistent: false, + }, + ...treeData.map((node) => { + return { + ...node, + filterPriority: node.filterPriority + 1, + displayPriority: node.displayPriority + 1, + }; + }), + ]); + } + + function removeSection(name: string) { + setTreeData(treeData.filter((node) => node.name !== name)); + } + + function moveItem(name: string, direction: "up" | "down") { + const group = treeData.find((node) => node.name === name)!; + const index = + orderBy === "display" ? group.displayPriority : group.filterPriority; + const swapWithIndex = index + (direction === "down" ? 1 : -1); + + if (swapWithIndex < 0 || swapWithIndex >= treeData.length) { + return; + } + const swapWith = treeData.find( + (node) => + (orderBy === "display" ? node.displayPriority : node.filterPriority) === + swapWithIndex + ); + + if (orderBy == "display") { + group.displayPriority = swapWithIndex; + swapWith!.displayPriority = index; + } else { + group.filterPriority = swapWithIndex; + swapWith!.filterPriority = index; + } + setTreeData([...treeData]); + } + + function setItem(name: string, newName: string, regex: string) { + setTreeData( + treeData.map((node) => { + if (node.name === name) { + return { + ...node, + regex: new RegExp(regex), + name: newName, + }; + } + return node; + }) + ); + } + + const Node = React.memo(function Node({ data }: { data: Group }) { + return ( + + + + + + {data.name} + + {data.regex.source} + + + + + + + + + + + ); + }); + + return ( + e.stopPropagation()} + > + + + + + + + + + + + + {treeData + .sort((a, b) => { + if (orderBy === "display") { + return a.displayPriority - b.displayPriority; + } + return a.filterPriority - b.filterPriority; + }) + .map((node) => ( + + ))} + + + ); +} diff --git a/torchci/components/HudGroupingSettings/defaults.ts b/torchci/components/HudGroupingSettings/defaults.ts new file mode 100644 index 0000000000..52d8da86a2 --- /dev/null +++ b/torchci/components/HudGroupingSettings/defaults.ts @@ -0,0 +1,174 @@ +import { Group } from "components/HudGroupingSettings/mainPageSettingsUtils"; + +const GROUP_MEMORY_LEAK_CHECK = "Memory Leak Check"; +const GROUP_RERUN_DISABLED_TESTS = "Rerun Disabled Tests"; +export const GROUP_UNSTABLE = "Unstable"; +const GROUP_PERIODIC = "Periodic"; +const GROUP_SLOW = "Slow"; +const GROUP_LINT = "Lint"; +const GROUP_INDUCTOR = "Inductor"; +const GROUP_ANDROID = "Android"; +const GROUP_ROCM = "ROCm"; +const GROUP_XLA = "XLA"; +const GROUP_LINUX = "Linux"; +const GROUP_BINARY_LINUX = "Binary Linux"; +const GROUP_BINARY_WINDOWS = "Binary Windows"; +const GROUP_ANNOTATIONS_AND_LABELING = "Annotations and labeling"; +const GROUP_DOCKER = "Docker"; +const GROUP_WINDOWS = "Windows"; +const GROUP_CALC_DOCKER_IMAGE = "GitHub calculate-docker-image"; +const GROUP_CI_DOCKER_IMAGE_BUILDS = "CI Docker Image Builds"; +const GROUP_CI_CIRCLECI_PYTORCH_IOS = "ci/circleci: pytorch_ios"; +const GROUP_IOS = "iOS"; +const GROUP_MAC = "Mac"; +const GROUP_PARALLEL = "Parallel"; +const GROUP_DOCS = "Docs"; +const GROUP_LIBTORCH = "Libtorch"; +export const GROUP_OTHER = "other"; + +// Jobs will be grouped with the first regex they match in this list +const groups = [ + { + regex: /mem_leak_check/, + name: GROUP_MEMORY_LEAK_CHECK, + persistent: true, + }, + { + regex: /rerun_disabled_tests/, + name: GROUP_RERUN_DISABLED_TESTS, + persistent: true, + }, + { + regex: /unstable/, + name: GROUP_UNSTABLE, + }, + { + regex: /periodic/, + name: GROUP_PERIODIC, + }, + { + regex: /slow/, + name: GROUP_SLOW, + }, + { + regex: /Lint/, + name: GROUP_LINT, + }, + { + regex: /inductor/, + name: GROUP_INDUCTOR, + }, + { + regex: /android/, + name: GROUP_ANDROID, + }, + { + regex: /rocm/, + name: GROUP_ROCM, + }, + { + regex: /-xla/, + name: GROUP_XLA, + }, + { + regex: /(\slinux-|sm86)/, + name: GROUP_LINUX, + }, + { + regex: /linux-binary/, + name: GROUP_BINARY_LINUX, + }, + { + regex: /windows-binary/, + name: GROUP_BINARY_WINDOWS, + }, + { + regex: + /(Add annotations )|(Close stale pull requests)|(Label PRs & Issues)|(Triage )|(Update S3 HTML indices)|(is-properly-labeled)|(Facebook CLA Check)|(auto-label-rocm)/, + name: GROUP_ANNOTATIONS_AND_LABELING, + }, + { + regex: + /(ci\/circleci: docker-pytorch-)|(ci\/circleci: ecr_gc_job_)|(ci\/circleci: docker_for_ecr_gc_build_job)|(Garbage Collect ECR Images)/, + name: GROUP_DOCKER, + }, + { + regex: /\swin-/, + name: GROUP_WINDOWS, + }, + { + regex: / \/ calculate-docker-image/, + name: GROUP_CALC_DOCKER_IMAGE, + }, + { + regex: /docker-builds/, + name: GROUP_CI_DOCKER_IMAGE_BUILDS, + }, + { + regex: /ci\/circleci: pytorch_ios_/, + name: GROUP_CI_CIRCLECI_PYTORCH_IOS, + }, + { + regex: /ios-/, + name: GROUP_IOS, + }, + { + regex: /\smacos-/, + name: GROUP_MAC, + }, + { + regex: + /(ci\/circleci: pytorch_parallelnative_)|(ci\/circleci: pytorch_paralleltbb_)|(paralleltbb-linux-)|(parallelnative-linux-)/, + name: GROUP_PARALLEL, + }, + { + regex: /(docs push)|(docs build)/, + name: GROUP_DOCS, + }, + { + regex: /libtorch/, + name: GROUP_LIBTORCH, + }, +]; + +// Jobs on HUD home page will be sorted according to this list, with anything left off at the end +// Reorder elements in this list to reorder the groups on the HUD +const HUD_GROUP_SORTING = [ + GROUP_LINT, + GROUP_LINUX, + GROUP_WINDOWS, + GROUP_IOS, + GROUP_MAC, + GROUP_ROCM, + GROUP_XLA, + GROUP_PARALLEL, + GROUP_LIBTORCH, + GROUP_ANDROID, + GROUP_BINARY_LINUX, + GROUP_DOCKER, + GROUP_CALC_DOCKER_IMAGE, + GROUP_CI_DOCKER_IMAGE_BUILDS, + GROUP_CI_CIRCLECI_PYTORCH_IOS, + GROUP_PERIODIC, + GROUP_SLOW, + GROUP_DOCS, + GROUP_INDUCTOR, + GROUP_ANNOTATIONS_AND_LABELING, + GROUP_OTHER, + GROUP_BINARY_WINDOWS, + GROUP_MEMORY_LEAK_CHECK, + GROUP_RERUN_DISABLED_TESTS, + GROUP_UNSTABLE, +]; + +export function getDefaultGroupSettings(): Group[] { + return groups.map((g, i) => { + return { + name: g.name, + regex: g.regex, + filterPriority: i, + displayPriority: HUD_GROUP_SORTING.indexOf(g.name), + persistent: g.persistent ?? false, + }; + }); +} diff --git a/torchci/components/HudGroupingSettings/hudGroupingSettings.ts b/torchci/components/HudGroupingSettings/hudGroupingSettings.ts new file mode 100644 index 0000000000..6b15d9161e --- /dev/null +++ b/torchci/components/HudGroupingSettings/hudGroupingSettings.ts @@ -0,0 +1,129 @@ +import { Group } from "components/HudGroupingSettings/mainPageSettingsUtils"; +import { isFailure } from "lib/JobClassifierUtil"; +import { getOpenUnstableIssues } from "lib/jobUtils"; +import { IssueData, RowData } from "lib/types"; +import { + getDefaultGroupSettings, + GROUP_OTHER, + GROUP_UNSTABLE, +} from "./defaults"; +import { getStoredTreeData } from "./mainPageSettingsUtils"; + +function getGroupSettings() { + const groups = getStoredTreeData() ?? getDefaultGroupSettings(); + groups.push({ + name: GROUP_OTHER, + regex: /.*/, + filterPriority: groups.length, + displayPriority: groups.length, + persistent: false, + }); + return groups; +} + +export function getGroupingData( + shaGrid: RowData[], + jobNames: Set, + showUnstableGroup: boolean, + unstableIssues?: IssueData[] +) { + // Construct Job Groupping Mapping + const groupSettings = getGroupSettings(); + + const groupNameMapping = new Map>(); // group -> [job names] + + // Track which jobs have failures + const jobsWithFailures = new Set(); + + // First pass: check failures for each job across all commits + for (const name of jobNames) { + // Check if this job has failures in any commit + const hasFailure = shaGrid.some((row) => { + const job = row.nameToJobs.get(name); + return job && isFailure(job.conclusion); + }); + + if (hasFailure) { + jobsWithFailures.add(name); + } + } + + for (const name of jobNames) { + const groupName = classifyGroup( + name, + showUnstableGroup, + groupSettings, + unstableIssues + ); + const jobsInGroup = groupNameMapping.get(groupName) ?? []; + jobsInGroup.push(name); + groupNameMapping.set(groupName, jobsInGroup); + } + + // Calculate which groups have failures + const groupsWithFailures = new Set(); + for (const [groupName, jobs] of groupNameMapping.entries()) { + if (jobs.some((jobName) => jobsWithFailures.has(jobName))) { + groupsWithFailures.add(groupName); + } + } + + return { + groupNameMapping, + jobsWithFailures, + groupsWithFailures, + groupSettings, + }; +} + +// Accepts a list of group names and returns that list sorted according to +// the order defined in HUD_GROUP_SORTING +export function sortGroupNamesForHUD( + groupNames: string[], + groups: Group[] +): string[] { + let result = groupNames.sort((a, b) => { + return ( + groups.find((g) => g.name === a)!.displayPriority - + groups.find((g) => g.name === b)!.displayPriority + ); + }); + + // Be flexible in case against any groups were left out of HUD_GROUP_SORTING + let remaining = groupNames.filter((x) => !result.includes(x)); + + result = result.concat(remaining); + return result; +} + +export function classifyGroup( + jobName: string, + showUnstableGroup: boolean, + groups: Group[], + unstableIssues?: IssueData[] +): string { + const openUnstableIssues = getOpenUnstableIssues(jobName, unstableIssues); + let assignedGroup = undefined; + for (const group of groups.sort( + (a, b) => a.filterPriority - b.filterPriority + )) { + if (jobName.match(group.regex)) { + assignedGroup = group; + break; + } + } + + // Check if the job has been marked as unstable but doesn't include the + // unstable keyword. + if (!showUnstableGroup && assignedGroup?.persistent) { + // If the unstable group is not being shown, then persistent groups (mem + // leak check, rerun disabled tests) should not be overwritten + return assignedGroup.name; + } + + if (openUnstableIssues !== undefined && openUnstableIssues.length !== 0) { + return GROUP_UNSTABLE; + } + + return assignedGroup === undefined ? GROUP_OTHER : assignedGroup.name; +} diff --git a/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts new file mode 100644 index 0000000000..109888e271 --- /dev/null +++ b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts @@ -0,0 +1,50 @@ +export type Group = { + name: string; + regex: RegExp; + filterPriority: number; + displayPriority: number; + persistent: boolean; +}; + +export function saveTreeData(treeData: Group[]) { + // Convert RegExp objects to a format that can be serialized + const serializable = treeData.map((group) => ({ + ...group, + regex: group.regex.source, // Store only the regex pattern as a string + })); + + const setting = JSON.stringify(serializable); + localStorage.setItem("hud_group_settings", setting); +} + +export function getStoredTreeData(): Group[] { + try { + // Try to load saved tree data from localStorage + const stored = localStorage.getItem("hud_group_settings"); + + if (!stored) return []; + + const parsed = JSON.parse(stored); + + // Convert the stored string patterns back to RegExp objects + return parsed.map((item: any) => ({ + ...item, + regex: new RegExp(item.regex || ""), + })); + } catch (error) { + console.error("Error loading stored group settings:", error); + return []; + } +} + +export function getNonDupNewName(treeData: Group[]) { + let i = 0; + while (isDupName(treeData, `New Group ${i}`)) { + i++; + } + return `New Group ${i}`; +} + +export function isDupName(treeData: Group[], name: string): boolean { + return treeData.some((node) => node.name === name); +} diff --git a/torchci/components/common/ValidatedTextField.tsx b/torchci/components/common/ValidatedTextField.tsx new file mode 100644 index 0000000000..c20a0f25dc --- /dev/null +++ b/torchci/components/common/ValidatedTextField.tsx @@ -0,0 +1,33 @@ +import { TextField } from "@mui/material"; +import { useState } from "react"; + +// Text field but with validation +export function ValidatedTextField({ + name, + isValid, + initialValue, + errorMessage = "Invalid", +}: { + name: string; + isValid: (value: string) => boolean; + initialValue: string; + errorMessage?: string; +}) { + const [value, setValue] = useState(initialValue); + const [valid, setValid] = useState(true); + function onChangeWrapper(e: React.ChangeEvent) { + const newValue = e.target.value; + setValue(newValue); + setValid(isValid(newValue)); + } + + return ( + + ); +} diff --git a/torchci/lib/JobClassifierUtil.ts b/torchci/lib/JobClassifierUtil.ts index 75aaca3ff6..489cfa0a14 100644 --- a/torchci/lib/JobClassifierUtil.ts +++ b/torchci/lib/JobClassifierUtil.ts @@ -351,56 +351,7 @@ export function getConclusionSeverityForSorting(conclusion?: string): number { } } -export function getGroupingData( - shaGrid: RowData[], - jobNames: Set, - showUnstableGroup: boolean, - unstableIssues?: IssueData[] -) { - // Construct Job Groupping Mapping - const groupNameMapping = new Map>(); // group -> [job names] - - // Track which jobs have failures - const jobsWithFailures = new Set(); - - // First pass: check failures for each job across all commits - for (const name of jobNames) { - // Check if this job has failures in any commit - const hasFailure = shaGrid.some((row) => { - const job = row.nameToJobs.get(name); - return job && isFailure(job.conclusion); - }); - - if (hasFailure) { - jobsWithFailures.add(name); - } - } - - // Second pass: group jobs - for (const name of jobNames) { - const groupName = classifyGroup(name, showUnstableGroup, unstableIssues); - const jobsInGroup = groupNameMapping.get(groupName) ?? []; - jobsInGroup.push(name); - groupNameMapping.set(groupName, jobsInGroup); - } - - // Calculate which groups have failures - const groupsWithFailures = new Set(); - for (const [groupName, jobs] of groupNameMapping.entries()) { - if (jobs.some((jobName) => jobsWithFailures.has(jobName))) { - groupsWithFailures.add(groupName); - } - } - - return { - shaGrid, - groupNameMapping, - jobsWithFailures, - groupsWithFailures, - }; -} - -export function isPersistentGroup(name: string) { +export function isPersistentGroup(groups: Group[], name: string) { return ( groups.filter((group) => group.name == name && group.persistent).length !== 0 diff --git a/torchci/package.json b/torchci/package.json index 891d35c0d7..70857bbaae 100644 --- a/torchci/package.json +++ b/torchci/package.json @@ -22,6 +22,7 @@ "@codemirror/state": "^0.20.0", "@codemirror/theme-one-dark": "^0.20.0", "@codemirror/view": "^0.20.7", + "@dnd-kit/core": "^6.3.1", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^7.3.1", @@ -55,6 +56,7 @@ "pino-std-serializers": "^7.0.0", "probot": "^12.3.3", "react": "^18.3.1", + "react-dnd": "^16.0.1", "react-dom": "^18.3.1", "react-ga4": "^2.1.0", "react-icons": "^4.3.1", @@ -62,6 +64,8 @@ "react-markdown": "^8.0.3", "react-router-dom": "^6.26.2", "react-use-clipboard": "^1.0.8", + "react-vtree": "^2.0.4", + "react-window": "^1.8.10", "recharts": "^2.5.0", "shlex": "^2.1.0", "swr": "^2.2.5", diff --git a/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx b/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx index ff0c8f60c4..1aa9c9b73c 100644 --- a/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx +++ b/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx @@ -16,12 +16,7 @@ import JobFilterInput from "components/job/JobFilterInput"; import JobTooltip from "components/job/JobTooltip"; import SettingsPanel from "components/SettingsPanel"; import { fetcher } from "lib/GeneralUtils"; -import { - getGroupingData, - groups, - isUnstableGroup, - sortGroupNamesForHUD, -} from "lib/JobClassifierUtil"; +import { isUnstableGroup } from "lib/JobClassifierUtil"; import { isFailedJob, isRerunDisabledTestsJob, @@ -195,9 +190,10 @@ function HudJobCells({ unstableIssues: IssueData[]; params: HudParams; }) { - let groupNames = groups.map((group) => group.name).concat("other"); const { expandedGroups, setExpandedGroups, groupNameMapping } = useContext(GroupingContext); + const groupNames = Array.from(groupNameMapping.keys()); + return ( <> {names.map((name) => { @@ -580,7 +576,7 @@ function GroupedHudTable({ params }: { params: HudParams }) { }, [router, useGrouping]); const groupNames = Array.from(groupNameMapping.keys()); - let names = sortGroupNamesForHUD(groupNames); + let names = sortGroupNamesForHUD(groupNames, groupSettings); if (useGrouping) { expandedGroups.forEach((group) => { @@ -598,7 +594,7 @@ function GroupedHudTable({ params }: { params: HudParams }) { } } else { names = [...jobNames]; - groups.forEach((group) => { + groupSettings.forEach((group) => { if ( groupNames.includes(group.name) && (group.persistent || diff --git a/torchci/yarn.lock b/torchci/yarn.lock index 8356314535..5f1188c9b5 100644 --- a/torchci/yarn.lock +++ b/torchci/yarn.lock @@ -4748,6 +4748,15 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dnd-core@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-16.0.1.tgz#a1c213ed08961f6bd1959a28bb76f1a868360d19" + integrity sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng== + dependencies: + "@react-dnd/asap" "^5.0.1" + "@react-dnd/invariant" "^4.0.1" + redux "^4.2.0" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" @@ -5863,7 +5872,7 @@ hast-util-whitespace@^2.0.0: resolved "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz" integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg== -hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6981,6 +6990,11 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + merge-descriptors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" @@ -8032,6 +8046,17 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" +react-dnd@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-16.0.1.tgz#2442a3ec67892c60d40a1559eef45498ba26fa37" + integrity sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q== + dependencies: + "@react-dnd/invariant" "^4.0.1" + "@react-dnd/shallowequal" "^4.0.1" + dnd-core "^16.0.1" + fast-deep-equal "^3.1.3" + hoist-non-react-statics "^3.3.2" + react-dom@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -8153,6 +8178,21 @@ react-use-clipboard@^1.0.8: dependencies: copy-to-clipboard "^3.3.1" +react-vtree@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-vtree/-/react-vtree-2.0.4.tgz#340e64255f5f4ec6f4c35dc44a7036f7fcd98bc5" + integrity sha512-UOld0VqyAZrryF06K753X4bcEVN6/wW831exvVlMZeZAVHk9KXnlHs4rpqDAeoiBgUwJqoW/rtn0hwsokRRxPA== + dependencies: + "@babel/runtime" "^7.11.0" + +react-window@^1.8.10: + version "1.8.10" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.10.tgz#9e6b08548316814b443f7002b1cf8fd3a1bdde03" + integrity sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -8216,6 +8256,13 @@ reduce-css-calc@^2.1.8: css-unit-converter "^1.1.1" postcss-value-parser "^3.3.0" +redux@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + regenerator-runtime@^0.13.4: version "0.13.11" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" @@ -8924,6 +8971,11 @@ tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tslib@^2.3.1, tslib@^2.5.0: version "2.5.3" resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz" From ddf3884564f18472d7345cf8a634dc1ad32ea6e6 Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Fri, 28 Mar 2025 11:41:57 -0700 Subject: [PATCH 02/12] tc --- .../HudGroupingSettings/MainPageSettings.tsx | 117 ++++++++++++++++-- torchci/package.json | 1 + 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.tsx b/torchci/components/HudGroupingSettings/MainPageSettings.tsx index 6e6c2ce6fb..5ec55e9448 100644 --- a/torchci/components/HudGroupingSettings/MainPageSettings.tsx +++ b/torchci/components/HudGroupingSettings/MainPageSettings.tsx @@ -1,3 +1,18 @@ +import { + closestCenter, + DndContext, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, +} from "@dnd-kit/core"; +import { + SortableContext, + sortableKeyboardCoordinates, + useSortable, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; import { KeyboardArrowDown } from "@mui/icons-material"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; import { @@ -22,6 +37,7 @@ import { isDupName, saveTreeData, } from "./mainPageSettingsUtils"; +import { set } from "lodash"; function validRegex(value: string) { try { @@ -119,6 +135,19 @@ export default function SettingsModal({ }) { const [treeData, setTreeData] = useState(getStoredTreeData()); const [orderBy, setOrderBy] = useState<"display" | "filter">("display"); + const treeDataOrdered = treeData.sort((a, b) => { + if (orderBy === "display") { + return a.displayPriority - b.displayPriority; + } + return a.filterPriority - b.filterPriority; + }); + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); function addSection() { setTreeData([ @@ -184,8 +213,16 @@ export default function SettingsModal({ } const Node = React.memo(function Node({ data }: { data: Group }) { + const { attributes, listeners, setNodeRef, transform, transition } = + useSortable({ id: data.name }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + return ( - + ); }); + function handleDragEnd(event: any) { + const { active, over } = event; + const priority = orderBy === "display" ? "displayPriority" : "filterPriority"; + console.log(active.id, over.id); + treeDataOrdered.forEach((node) => { + console.log(node.name, node[priority]); + }) + + const oldIndex = treeData.find((node) => node.name === active.id)![priority]; + const newIndex = treeData.find((node) => node.name === over.id)![priority]; + console.log(newIndex, oldIndex); + if (oldIndex < newIndex) { + setTreeData( + treeData.map((node) => { + if (node[priority] === oldIndex) { + return { + ...node, + [priority]: newIndex, + }; + } else if (oldIndex <= node[priority] && node[priority] <= newIndex) { + return { + ...node, + [orderBy]: node[priority] - 1, + }; + } + return node; + }) + ); + } + if (newIndex < oldIndex) { + setTreeData( + treeData.map((node) => { + if (node[priority] === oldIndex) { + return { + ...node, + [priority]: newIndex, + }; + } else if (newIndex <= node[priority] && node[priority] <= oldIndex) { + return { + ...node, + [priority]: node[priority] + 1, + }; + } + return node; + }) + ); + } + // if (active.id !== over.id) { + // setItems((items) => { + // const oldIndex = items.indexOf(active.id); + // const newIndex = items.indexOf(over.id); + // }); + // } + } return ( - {treeData - .sort((a, b) => { - if (orderBy === "display") { - return a.displayPriority - b.displayPriority; - } - return a.filterPriority - b.filterPriority; - }) - .map((node) => ( - - ))} + + node.name)} + strategy={verticalListSortingStrategy} + > + {treeDataOrdered.map((id) => ( + + ))} + + ); diff --git a/torchci/package.json b/torchci/package.json index 70857bbaae..cde01d2086 100644 --- a/torchci/package.json +++ b/torchci/package.json @@ -23,6 +23,7 @@ "@codemirror/theme-one-dark": "^0.20.0", "@codemirror/view": "^0.20.7", "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^7.3.1", From dc8365eaf29a99c6d25cfd6ce69a07ea65bb245e Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Fri, 28 Mar 2025 11:44:08 -0700 Subject: [PATCH 03/12] tc --- torchci/components/HudGroupingSettings/MainPageSettings.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.tsx b/torchci/components/HudGroupingSettings/MainPageSettings.tsx index 5ec55e9448..fd5675960d 100644 --- a/torchci/components/HudGroupingSettings/MainPageSettings.tsx +++ b/torchci/components/HudGroupingSettings/MainPageSettings.tsx @@ -317,6 +317,7 @@ export default function SettingsModal({ maxWidth="xl" onClose={handleClose} onClick={(e) => e.stopPropagation()} + style={{zIndex: 3000}} > Date: Fri, 28 Mar 2025 11:46:50 -0700 Subject: [PATCH 04/12] tc --- .../HudGroupingSettings/MainPageSettings.tsx | 68 ++++--------------- 1 file changed, 15 insertions(+), 53 deletions(-) diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.tsx b/torchci/components/HudGroupingSettings/MainPageSettings.tsx index fd5675960d..5223cb40dc 100644 --- a/torchci/components/HudGroupingSettings/MainPageSettings.tsx +++ b/torchci/components/HudGroupingSettings/MainPageSettings.tsx @@ -13,8 +13,6 @@ import { verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; -import { KeyboardArrowDown } from "@mui/icons-material"; -import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; import { Button, Dialog, @@ -37,7 +35,6 @@ import { isDupName, saveTreeData, } from "./mainPageSettingsUtils"; -import { set } from "lodash"; function validRegex(value: string) { try { @@ -172,31 +169,6 @@ export default function SettingsModal({ setTreeData(treeData.filter((node) => node.name !== name)); } - function moveItem(name: string, direction: "up" | "down") { - const group = treeData.find((node) => node.name === name)!; - const index = - orderBy === "display" ? group.displayPriority : group.filterPriority; - const swapWithIndex = index + (direction === "down" ? 1 : -1); - - if (swapWithIndex < 0 || swapWithIndex >= treeData.length) { - return; - } - const swapWith = treeData.find( - (node) => - (orderBy === "display" ? node.displayPriority : node.filterPriority) === - swapWithIndex - ); - - if (orderBy == "display") { - group.displayPriority = swapWithIndex; - swapWith!.displayPriority = index; - } else { - group.filterPriority = swapWithIndex; - swapWith!.filterPriority = index; - } - setTreeData([...treeData]); - } - function setItem(name: string, newName: string, regex: string) { setTreeData( treeData.map((node) => { @@ -222,7 +194,13 @@ export default function SettingsModal({ }; return ( - + {data.regex.source} - - { - console.log(node.name, node[priority]); - }) - - const oldIndex = treeData.find((node) => node.name === active.id)![priority]; + const priority = + orderBy === "display" ? "displayPriority" : "filterPriority"; + const oldIndex = treeData.find((node) => node.name === active.id)![ + priority + ]; const newIndex = treeData.find((node) => node.name === over.id)![priority]; - console.log(newIndex, oldIndex); if (oldIndex < newIndex) { setTreeData( treeData.map((node) => { @@ -277,7 +246,7 @@ export default function SettingsModal({ } else if (oldIndex <= node[priority] && node[priority] <= newIndex) { return { ...node, - [orderBy]: node[priority] - 1, + [priority]: node[priority] - 1, }; } return node; @@ -302,13 +271,6 @@ export default function SettingsModal({ }) ); } - // if (active.id !== over.id) { - // setItems((items) => { - // const oldIndex = items.indexOf(active.id); - // const newIndex = items.indexOf(over.id); - - // }); - // } } return ( e.stopPropagation()} - style={{zIndex: 3000}} + style={{ zIndex: 400000 }} > {treeDataOrdered.map((id) => ( - + ))} From 0bd89b642909bc3f229f48c87751c885ea8ea46d Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Fri, 28 Mar 2025 11:58:13 -0700 Subject: [PATCH 05/12] tc --- .../HudGroupingSettings/MainPageSettings.tsx | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.tsx b/torchci/components/HudGroupingSettings/MainPageSettings.tsx index 5223cb40dc..eaa2feca51 100644 --- a/torchci/components/HudGroupingSettings/MainPageSettings.tsx +++ b/torchci/components/HudGroupingSettings/MainPageSettings.tsx @@ -35,6 +35,7 @@ import { isDupName, saveTreeData, } from "./mainPageSettingsUtils"; +import { DragHandle } from "@mui/icons-material"; function validRegex(value: string) { try { @@ -62,7 +63,7 @@ function EditSectionDialog({ return ( <> - + + - + + {data.name} @@ -220,15 +213,19 @@ export default function SettingsModal({ name={data.name} setGroup={setItem} /> - + ); }); + function handleDragEnd(event: any) { const { active, over } = event; + if (active == null || over == null) { + return; + } const priority = orderBy === "display" ? "displayPriority" : "filterPriority"; const oldIndex = treeData.find((node) => node.name === active.id)![ @@ -279,7 +276,6 @@ export default function SettingsModal({ maxWidth="xl" onClose={handleClose} onClick={(e) => e.stopPropagation()} - style={{ zIndex: 400000 }} > {treeDataOrdered.map((id) => ( - + ))} From 0a8af5bb5da1fa75d15821dcd82fc1a632b80b76 Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Tue, 16 Sep 2025 11:13:09 -0700 Subject: [PATCH 06/12] tc --- .../HudGroupingSettings/MainPageSettings.tsx | 101 +++++++++++++++--- .../hudGroupingSettings.ts | 1 + .../mainPageSettingsUtils.ts | 4 +- torchci/lib/JobClassifierUtil.ts | 48 ++------- .../[repoName]/[branch]/[[...page]].tsx | 30 ++++-- torchci/yarn.lock | 55 ++++++++-- 6 files changed, 168 insertions(+), 71 deletions(-) diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.tsx b/torchci/components/HudGroupingSettings/MainPageSettings.tsx index eaa2feca51..138268c02e 100644 --- a/torchci/components/HudGroupingSettings/MainPageSettings.tsx +++ b/torchci/components/HudGroupingSettings/MainPageSettings.tsx @@ -1,18 +1,17 @@ import { closestCenter, DndContext, - KeyboardSensor, PointerSensor, useSensor, useSensors, } from "@dnd-kit/core"; import { SortableContext, - sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; +import { DragHandle } from "@mui/icons-material"; import { Button, Dialog, @@ -35,7 +34,6 @@ import { isDupName, saveTreeData, } from "./mainPageSettingsUtils"; -import { DragHandle } from "@mui/icons-material"; function validRegex(value: string) { try { @@ -63,7 +61,13 @@ function EditSectionDialog({ return ( <> - + void }) { + const [open, setOpen] = useState(false); + + return ( + <> + + setOpen(false)} + aria-modal + > + + + + Are you sure you want to reset to default group settings? + + + + + + + + + + ); +} + export default function SettingsModal({ visible, handleClose, @@ -140,9 +192,7 @@ export default function SettingsModal({ return a.filterPriority - b.filterPriority; }); - const sensors = useSensors( - useSensor(PointerSensor), - ); + const sensors = useSensors(useSensor(PointerSensor)); function addSection() { setTreeData([ @@ -164,7 +214,25 @@ export default function SettingsModal({ } function removeSection(name: string) { - setTreeData(treeData.filter((node) => node.name !== name)); + const removedNode = treeData.find((node) => node.name === name); + const filterPriority = removedNode!.filterPriority; + const displayPriority = removedNode!.displayPriority; + const newTreeData = treeData + .filter((node) => node.name !== name) + .map((node) => { + return { + ...node, + filterPriority: + node.filterPriority > filterPriority + ? node.filterPriority - 1 + : node.filterPriority, + displayPriority: + node.displayPriority > displayPriority + ? node.displayPriority - 1 + : node.displayPriority, + }; + }); + setTreeData(newTreeData); } function setItem(name: string, newName: string, regex: string) { @@ -274,7 +342,10 @@ export default function SettingsModal({ open={visible} fullWidth={true} maxWidth="xl" - onClose={handleClose} + onClose={() => { + saveTreeData(treeData); + handleClose(); + }} onClick={(e) => e.stopPropagation()} > Save - + /> @@ -325,7 +394,7 @@ export default function SettingsModal({ strategy={verticalListSortingStrategy} > {treeDataOrdered.map((id) => ( - + ))} diff --git a/torchci/components/HudGroupingSettings/hudGroupingSettings.ts b/torchci/components/HudGroupingSettings/hudGroupingSettings.ts index 6b15d9161e..83735de5f3 100644 --- a/torchci/components/HudGroupingSettings/hudGroupingSettings.ts +++ b/torchci/components/HudGroupingSettings/hudGroupingSettings.ts @@ -69,6 +69,7 @@ export function getGroupingData( } return { + shaGrid, groupNameMapping, jobsWithFailures, groupsWithFailures, diff --git a/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts index 109888e271..609730c0bb 100644 --- a/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts +++ b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts @@ -39,10 +39,10 @@ export function getStoredTreeData(): Group[] { export function getNonDupNewName(treeData: Group[]) { let i = 0; - while (isDupName(treeData, `New Group ${i}`)) { + while (isDupName(treeData, `NEW GROUP ${i}`)) { i++; } - return `New Group ${i}`; + return `NEW GROUP ${i}`; } export function isDupName(treeData: Group[], name: string): boolean { diff --git a/torchci/lib/JobClassifierUtil.ts b/torchci/lib/JobClassifierUtil.ts index 489cfa0a14..cde9b552fe 100644 --- a/torchci/lib/JobClassifierUtil.ts +++ b/torchci/lib/JobClassifierUtil.ts @@ -1,6 +1,7 @@ import { GroupedJobStatus, JobStatus } from "components/job/GroupJobConclusion"; import { getOpenUnstableIssues } from "lib/jobUtils"; import { IssueData, RowData } from "./types"; +import { Group } from "components/HudGroupingSettings/mainPageSettingsUtils"; const GROUP_MEMORY_LEAK_CHECK = "Memory Leak Check"; const GROUP_RERUN_DISABLED_TESTS = "Rerun Disabled Tests"; @@ -157,48 +158,19 @@ export const groups = [ }, ]; -// Jobs on HUD home page will be sorted according to this list, with anything left off at the end -// Reorder elements in this list to reorder the groups on the HUD -const HUD_GROUP_SORTING = [ - GROUP_LINT, - GROUP_LINUX, - GROUP_WINDOWS, - GROUP_IOS, - GROUP_MAC, - GROUP_ROCM, - GROUP_XPU, - GROUP_XLA, - GROUP_OTHER_VIABLE_STRICT_BLOCKING, // placed after the last group that tends to have viable/strict blocking jobs - GROUP_VLLM, - GROUP_PARALLEL, - GROUP_LIBTORCH, - GROUP_ANDROID, - GROUP_BINARY_LINUX, - GROUP_DOCKER, - GROUP_CALC_DOCKER_IMAGE, - GROUP_CI_DOCKER_IMAGE_BUILDS, - GROUP_CI_CIRCLECI_PYTORCH_IOS, - GROUP_PERIODIC, - GROUP_SLOW, - GROUP_DOCS, - GROUP_INDUCTOR, - GROUP_INDUCTOR_PERIODIC, - GROUP_ANNOTATIONS_AND_LABELING, - GROUP_BINARY_WINDOWS, - GROUP_MEMORY_LEAK_CHECK, - GROUP_RERUN_DISABLED_TESTS, - // These two groups should always be at the end - GROUP_OTHER, - GROUP_UNSTABLE, -]; // Accepts a list of group names and returns that list sorted according to // the order defined in HUD_GROUP_SORTING -export function sortGroupNamesForHUD(groupNames: string[]): string[] { +export function sortGroupNamesForHUD( + groupNames: string[], + groupSettings: Group[] +): string[] { let result: string[] = []; - for (const group of HUD_GROUP_SORTING) { - if (groupNames.includes(group)) { - result.push(group); + for (const group of groupSettings.sort( + (a, b) => a.displayPriority - b.displayPriority + )) { + if (groupNames.includes(group.name)) { + result.push(group.name); } } diff --git a/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx b/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx index 1aa9c9b73c..a4ed9e0b80 100644 --- a/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx +++ b/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx @@ -1,3 +1,4 @@ +import { Button } from "@mui/material"; import CheckBoxSelector from "components/common/CheckBoxSelector"; import CopyLink from "components/common/CopyLink"; import LoadingPage from "components/common/LoadingPage"; @@ -10,13 +11,15 @@ import { GroupHudTableHeader, passesGroupFilter, } from "components/hud/GroupHudTableHeaders"; +import { getGroupingData } from "components/HudGroupingSettings/hudGroupingSettings"; +import SettingsModal from "components/HudGroupingSettings/MainPageSettings"; import HudGroupedCell from "components/job/GroupJobConclusion"; import JobConclusion from "components/job/JobConclusion"; import JobFilterInput from "components/job/JobFilterInput"; import JobTooltip from "components/job/JobTooltip"; import SettingsPanel from "components/SettingsPanel"; import { fetcher } from "lib/GeneralUtils"; -import { isUnstableGroup } from "lib/JobClassifierUtil"; +import { isUnstableGroup, sortGroupNamesForHUD } from "lib/JobClassifierUtil"; import { isFailedJob, isRerunDisabledTestsJob, @@ -285,6 +288,7 @@ function FiltersAndSettings({}: {}) { const { jobFilter, handleSubmit } = useTableFilter(params); const [mergeEphemeralLF, setMergeEphemeralLF] = useContext(MergeLFContext); const [settingsPanelOpen, setSettingsPanelOpen] = useState(false); + const [groupingSettingsOpen, setGroupingSettingsOpen] = useState(false); const [hideUnstable, setHideUnstable] = usePreference("hideUnstable"); const [hideGreenColumns, setHideGreenColumns] = useHideGreenColumnsPreference(); @@ -340,6 +344,11 @@ function FiltersAndSettings({}: {}) { isOpen={settingsPanelOpen} onToggle={() => setSettingsPanelOpen(!settingsPanelOpen)} /> + + setGroupingSettingsOpen(false)} + /> ); } @@ -557,13 +566,18 @@ function GroupedHudTable({ params }: { params: HudParams }) { const [hideGreenColumns] = useHideGreenColumnsPreference(); const [useGrouping] = useGroupingPreference(params.nameFilter); - const { shaGrid, groupNameMapping, jobsWithFailures, groupsWithFailures } = - getGroupingData( - data ?? [], - jobNames, - (!useGrouping && hideUnstable) || (useGrouping && !hideUnstable), - unstableIssuesData ?? [] - ); + const { + shaGrid, + groupNameMapping, + jobsWithFailures, + groupsWithFailures, + groupSettings, + } = getGroupingData( + data ?? [], + jobNames, + (!useGrouping && hideUnstable) || (useGrouping && !hideUnstable), + unstableIssuesData ?? [] + ); const [expandedGroups, setExpandedGroups] = useState(new Set()); diff --git a/torchci/yarn.lock b/torchci/yarn.lock index 5f1188c9b5..375c28933e 100644 --- a/torchci/yarn.lock +++ b/torchci/yarn.lock @@ -1321,7 +1321,7 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.24.6", "@babel/runtime@^7.27.6", "@babel/runtime@^7.28.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.24.6", "@babel/runtime@^7.27.6", "@babel/runtime@^7.28.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw== @@ -1470,6 +1470,37 @@ style-mod "^4.0.0" w3c-keyname "^2.2.4" +"@dnd-kit/accessibility@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz#3b4202bd6bb370a0730f6734867785919beac6af" + integrity sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw== + dependencies: + tslib "^2.0.0" + +"@dnd-kit/core@^6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.3.1.tgz#4c36406a62c7baac499726f899935f93f0e6d003" + integrity sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ== + dependencies: + "@dnd-kit/accessibility" "^3.1.1" + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/sortable@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-10.0.0.tgz#1f9382b90d835cd5c65d92824fa9dafb78c4c3e8" + integrity sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg== + dependencies: + "@dnd-kit/utilities" "^3.2.2" + tslib "^2.0.0" + +"@dnd-kit/utilities@^3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b" + integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg== + dependencies: + tslib "^2.0.0" + "@emnapi/runtime@^1.4.4": version "1.4.5" resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.4.5.tgz#c67710d0661070f38418b6474584f159de38aba9" @@ -2793,6 +2824,21 @@ readable-stream "^3.6.0" split2 "^4.0.0" +"@react-dnd/asap@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-5.0.2.tgz#1f81f124c1cd6f39511c11a881cfb0f715343488" + integrity sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A== + +"@react-dnd/invariant@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-4.0.2.tgz#b92edffca10a26466643349fac7cdfb8799769df" + integrity sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw== + +"@react-dnd/shallowequal@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4" + integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== + "@remix-run/router@1.19.2": version "1.19.2" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273" @@ -8971,7 +9017,7 @@ tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0: +tslib@^2.0.0, tslib@^2.8.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -8986,11 +9032,6 @@ tslib@^2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== -tslib@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" From a4e68d5197b51ba6d0deee96e208734396a90832 Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Tue, 16 Sep 2025 11:17:34 -0700 Subject: [PATCH 07/12] tc --- .../HudGroupingSettings/defaults.ts | 37 +++- torchci/lib/JobClassifierUtil.ts | 189 +----------------- 2 files changed, 35 insertions(+), 191 deletions(-) diff --git a/torchci/components/HudGroupingSettings/defaults.ts b/torchci/components/HudGroupingSettings/defaults.ts index 52d8da86a2..5306b18b89 100644 --- a/torchci/components/HudGroupingSettings/defaults.ts +++ b/torchci/components/HudGroupingSettings/defaults.ts @@ -4,6 +4,7 @@ const GROUP_MEMORY_LEAK_CHECK = "Memory Leak Check"; const GROUP_RERUN_DISABLED_TESTS = "Rerun Disabled Tests"; export const GROUP_UNSTABLE = "Unstable"; const GROUP_PERIODIC = "Periodic"; +const GROUP_INDUCTOR_PERIODIC = "Inductor Periodic"; const GROUP_SLOW = "Slow"; const GROUP_LINT = "Lint"; const GROUP_INDUCTOR = "Inductor"; @@ -24,17 +25,26 @@ const GROUP_MAC = "Mac"; const GROUP_PARALLEL = "Parallel"; const GROUP_DOCS = "Docs"; const GROUP_LIBTORCH = "Libtorch"; +const GROUP_OTHER_VIABLE_STRICT_BLOCKING = "Other viable/strict blocking"; +const GROUP_XPU = "XPU"; +const GROUP_VLLM = "vLLM"; export const GROUP_OTHER = "other"; // Jobs will be grouped with the first regex they match in this list -const groups = [ +export const groups = [ { - regex: /mem_leak_check/, + regex: /vllm/, + name: GROUP_VLLM, + }, + { + // Weird regex because some names are too long and getting cut off + // TODO: figure out a better way to name the job or filter them + regex: /, mem_leak/, name: GROUP_MEMORY_LEAK_CHECK, persistent: true, }, { - regex: /rerun_disabled_tests/, + regex: /, rerun_/, name: GROUP_RERUN_DISABLED_TESTS, persistent: true, }, @@ -42,6 +52,14 @@ const groups = [ regex: /unstable/, name: GROUP_UNSTABLE, }, + { + regex: /^xpu/, + name: GROUP_XPU, + }, + { + regex: /inductor-periodic/, + name: GROUP_INDUCTOR_PERIODIC, + }, { regex: /periodic/, name: GROUP_PERIODIC, @@ -129,6 +147,12 @@ const groups = [ regex: /libtorch/, name: GROUP_LIBTORCH, }, + { + // This is a catch-all for jobs that are viable but strict blocking + // Excluding linux-binary-* jobs because they are already grouped further up + regex: /(pull)|(trunk)/, + name: GROUP_OTHER_VIABLE_STRICT_BLOCKING, + }, ]; // Jobs on HUD home page will be sorted according to this list, with anything left off at the end @@ -140,7 +164,10 @@ const HUD_GROUP_SORTING = [ GROUP_IOS, GROUP_MAC, GROUP_ROCM, + GROUP_XPU, GROUP_XLA, + GROUP_OTHER_VIABLE_STRICT_BLOCKING, // placed after the last group that tends to have viable/strict blocking jobs + GROUP_VLLM, GROUP_PARALLEL, GROUP_LIBTORCH, GROUP_ANDROID, @@ -153,11 +180,13 @@ const HUD_GROUP_SORTING = [ GROUP_SLOW, GROUP_DOCS, GROUP_INDUCTOR, + GROUP_INDUCTOR_PERIODIC, GROUP_ANNOTATIONS_AND_LABELING, - GROUP_OTHER, GROUP_BINARY_WINDOWS, GROUP_MEMORY_LEAK_CHECK, GROUP_RERUN_DISABLED_TESTS, + // These two groups should always be at the end + GROUP_OTHER, GROUP_UNSTABLE, ]; diff --git a/torchci/lib/JobClassifierUtil.ts b/torchci/lib/JobClassifierUtil.ts index cde9b552fe..37651cd88d 100644 --- a/torchci/lib/JobClassifierUtil.ts +++ b/torchci/lib/JobClassifierUtil.ts @@ -1,163 +1,7 @@ +import { Group } from "components/HudGroupingSettings/mainPageSettingsUtils"; import { GroupedJobStatus, JobStatus } from "components/job/GroupJobConclusion"; import { getOpenUnstableIssues } from "lib/jobUtils"; -import { IssueData, RowData } from "./types"; -import { Group } from "components/HudGroupingSettings/mainPageSettingsUtils"; - -const GROUP_MEMORY_LEAK_CHECK = "Memory Leak Check"; -const GROUP_RERUN_DISABLED_TESTS = "Rerun Disabled Tests"; -const GROUP_UNSTABLE = "Unstable"; -const GROUP_PERIODIC = "Periodic"; -const GROUP_INDUCTOR_PERIODIC = "Inductor Periodic"; -const GROUP_SLOW = "Slow"; -const GROUP_LINT = "Lint"; -const GROUP_INDUCTOR = "Inductor"; -const GROUP_ANDROID = "Android"; -const GROUP_ROCM = "ROCm"; -const GROUP_XLA = "XLA"; -const GROUP_LINUX = "Linux"; -const GROUP_BINARY_LINUX = "Binary Linux"; -const GROUP_BINARY_WINDOWS = "Binary Windows"; -const GROUP_ANNOTATIONS_AND_LABELING = "Annotations and labeling"; -const GROUP_DOCKER = "Docker"; -const GROUP_WINDOWS = "Windows"; -const GROUP_CALC_DOCKER_IMAGE = "GitHub calculate-docker-image"; -const GROUP_CI_DOCKER_IMAGE_BUILDS = "CI Docker Image Builds"; -const GROUP_CI_CIRCLECI_PYTORCH_IOS = "ci/circleci: pytorch_ios"; -const GROUP_IOS = "iOS"; -const GROUP_MAC = "Mac"; -const GROUP_PARALLEL = "Parallel"; -const GROUP_DOCS = "Docs"; -const GROUP_LIBTORCH = "Libtorch"; -const GROUP_OTHER_VIABLE_STRICT_BLOCKING = "Other viable/strict blocking"; -const GROUP_XPU = "XPU"; -const GROUP_VLLM = "vLLM"; -const GROUP_OTHER = "other"; - -// Jobs will be grouped with the first regex they match in this list -export const groups = [ - { - regex: /vllm/, - name: GROUP_VLLM, - }, - { - // Weird regex because some names are too long and getting cut off - // TODO: figure out a better way to name the job or filter them - regex: /, mem_leak/, - name: GROUP_MEMORY_LEAK_CHECK, - persistent: true, - }, - { - regex: /, rerun_/, - name: GROUP_RERUN_DISABLED_TESTS, - persistent: true, - }, - { - regex: /unstable/, - name: GROUP_UNSTABLE, - }, - { - regex: /^xpu/, - name: GROUP_XPU, - }, - { - regex: /inductor-periodic/, - name: GROUP_INDUCTOR_PERIODIC, - }, - { - regex: /periodic/, - name: GROUP_PERIODIC, - }, - { - regex: /slow/, - name: GROUP_SLOW, - }, - { - regex: /Lint/, - name: GROUP_LINT, - }, - { - regex: /inductor/, - name: GROUP_INDUCTOR, - }, - { - regex: /android/, - name: GROUP_ANDROID, - }, - { - regex: /rocm/, - name: GROUP_ROCM, - }, - { - regex: /-xla/, - name: GROUP_XLA, - }, - { - regex: /(\slinux-|sm86)/, - name: GROUP_LINUX, - }, - { - regex: /linux-binary/, - name: GROUP_BINARY_LINUX, - }, - { - regex: /windows-binary/, - name: GROUP_BINARY_WINDOWS, - }, - { - regex: - /(Add annotations )|(Close stale pull requests)|(Label PRs & Issues)|(Triage )|(Update S3 HTML indices)|(is-properly-labeled)|(Facebook CLA Check)|(auto-label-rocm)/, - name: GROUP_ANNOTATIONS_AND_LABELING, - }, - { - regex: - /(ci\/circleci: docker-pytorch-)|(ci\/circleci: ecr_gc_job_)|(ci\/circleci: docker_for_ecr_gc_build_job)|(Garbage Collect ECR Images)/, - name: GROUP_DOCKER, - }, - { - regex: /\swin-/, - name: GROUP_WINDOWS, - }, - { - regex: / \/ calculate-docker-image/, - name: GROUP_CALC_DOCKER_IMAGE, - }, - { - regex: /docker-builds/, - name: GROUP_CI_DOCKER_IMAGE_BUILDS, - }, - { - regex: /ci\/circleci: pytorch_ios_/, - name: GROUP_CI_CIRCLECI_PYTORCH_IOS, - }, - { - regex: /ios-/, - name: GROUP_IOS, - }, - { - regex: /\smacos-/, - name: GROUP_MAC, - }, - { - regex: - /(ci\/circleci: pytorch_parallelnative_)|(ci\/circleci: pytorch_paralleltbb_)|(paralleltbb-linux-)|(parallelnative-linux-)/, - name: GROUP_PARALLEL, - }, - { - regex: /(docs push)|(docs build)/, - name: GROUP_DOCS, - }, - { - regex: /libtorch/, - name: GROUP_LIBTORCH, - }, - { - // This is a catch-all for jobs that are viable but strict blocking - // Excluding linux-binary-* jobs because they are already grouped further up - regex: /(pull)|(trunk)/, - name: GROUP_OTHER_VIABLE_STRICT_BLOCKING, - }, -]; - +import { IssueData } from "./types"; // Accepts a list of group names and returns that list sorted according to // the order defined in HUD_GROUP_SORTING @@ -181,35 +25,6 @@ export function sortGroupNamesForHUD( return result; } -export function classifyGroup( - jobName: string, - showUnstableGroup: boolean, - unstableIssues?: IssueData[] -): string { - const openUnstableIssues = getOpenUnstableIssues(jobName, unstableIssues); - let assignedGroup = undefined; - for (const group of groups) { - if (jobName.match(group.regex)) { - assignedGroup = group; - break; - } - } - - // Check if the job has been marked as unstable but doesn't include the - // unstable keyword. - if (!showUnstableGroup && assignedGroup?.persistent) { - // If the unstable group is not being shown, then persistent groups (mem - // leak check, rerun disabled tests) should not be overwritten - return assignedGroup.name; - } - - if (openUnstableIssues !== undefined && openUnstableIssues.length !== 0) { - return GROUP_UNSTABLE; - } - - return assignedGroup === undefined ? GROUP_OTHER : assignedGroup.name; -} - export function getGroupConclusionChar(conclusion?: GroupedJobStatus): string { switch (conclusion) { case GroupedJobStatus.Success: From e0d64f8699e58486feab97e25b886d088495cd95 Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Tue, 16 Sep 2025 11:36:12 -0700 Subject: [PATCH 08/12] tc --- .../components/HudGroupingSettings/mainPageSettingsUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts index 609730c0bb..9d7cc429d2 100644 --- a/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts +++ b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts @@ -1,3 +1,5 @@ +import { getDefaultGroupSettings } from "./defaults"; + export type Group = { name: string; regex: RegExp; @@ -22,7 +24,7 @@ export function getStoredTreeData(): Group[] { // Try to load saved tree data from localStorage const stored = localStorage.getItem("hud_group_settings"); - if (!stored) return []; + if (!stored) return getDefaultGroupSettings(); const parsed = JSON.parse(stored); From d0f0915c0a09a7cd92e0891335c5fbb46ed65ab7 Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Tue, 16 Sep 2025 12:45:50 -0700 Subject: [PATCH 09/12] better default handling --- .../HudGroupingSettings/mainPageSettingsUtils.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts index 9d7cc429d2..a369caf3a0 100644 --- a/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts +++ b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts @@ -1,3 +1,4 @@ +import _ from "lodash"; import { getDefaultGroupSettings } from "./defaults"; export type Group = { @@ -9,7 +10,19 @@ export type Group = { }; export function saveTreeData(treeData: Group[]) { - // Convert RegExp objects to a format that can be serialized + if ( + _.isEqual( + treeData.sort((a, b) => a.name.localeCompare(b.name)), + getDefaultGroupSettings().sort((a, b) => a.name.localeCompare(b.name)) + ) + ) { + // If the current settings are the same as the default, remove from + // localStorage + localStorage.removeItem("hud_group_settings"); + return; + } + + // Convert RegExp objects to a format that can be seralized const serializable = treeData.map((group) => ({ ...group, regex: group.regex.source, // Store only the regex pattern as a string From 1761a9a23ef4092a59c98de8f4974192ca05e692 Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Fri, 19 Sep 2025 10:23:20 -0700 Subject: [PATCH 10/12] tc --- .../HudGroupingSettings/MainPageSettings.tsx | 309 ++++++++++++------ .../hudGroupingSettings.ts | 10 +- .../mainPageSettingsUtils.ts | 90 +++-- .../components/common/ValidatedTextField.tsx | 1 + .../[repoName]/[branch]/[[...page]].tsx | 4 + 5 files changed, 287 insertions(+), 127 deletions(-) diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.tsx b/torchci/components/HudGroupingSettings/MainPageSettings.tsx index 138268c02e..554d3904ba 100644 --- a/torchci/components/HudGroupingSettings/MainPageSettings.tsx +++ b/torchci/components/HudGroupingSettings/MainPageSettings.tsx @@ -11,7 +11,7 @@ import { verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; -import { DragHandle } from "@mui/icons-material"; +import { Check, CopyAll, DragHandle } from "@mui/icons-material"; import { Button, Dialog, @@ -32,7 +32,9 @@ import { getStoredTreeData, Group, isDupName, + parseTreeData, saveTreeData, + serializeTreeData, } from "./mainPageSettingsUtils"; function validRegex(value: string) { @@ -44,86 +46,187 @@ function validRegex(value: string) { } } -function EditSectionDialog({ +// MARK: Default Components + +function FormStack({ + children, + onSubmit, +}: { + children: React.ReactNode; + onSubmit: (e: React.FormEvent) => void; +}) { + return ( + + {children} + + ); +} + +function DefaultOpenDialogButton({ + text, + setOpen, +}: { + text: string; + setOpen: (open: boolean) => void; +}) { + return ( + + ); +} + +function DefaultDialog({ + open, + setOpen, + children, +}: { + open: boolean; + setOpen: (open: boolean) => void; + children: React.ReactNode; +}) { + return ( + setOpen(false)} + aria-modal + > + {children} + + ); +} + +// MARK: Specific Dialogs + +function ImportExportDialog({ treeData, - name, - setGroup, + setTreeData, }: { treeData: Group[]; - name: string; - setGroup: (name: string, newName: string, regex: string) => void; + setTreeData: (data: Group[]) => void; }) { const [open, setOpen] = useState(false); - - function isGoodName(value: string) { - return value == name || !isDupName(treeData, value); - } + const [justClicked, setJustClicked] = useState(false); return ( <> - - setOpen(false)} - aria-modal - > - - + + + {/* Copy current */} + + { e.preventDefault(); - // @ts-ignore - const regex = e.target[2].value; - // @ts-ignore - const newName = e.target[0].value; - if (!validRegex(regex) || !isGoodName(newName)) { + const data = new FormData(e.currentTarget); + const value = data.get("Import"); + const revived = parseTreeData(value as string); + if (revived === undefined) { return; } - setGroup(name, newName, regex); + setTreeData(revived); setOpen(false); }} > - node.name === name)?.regex.source ?? "" - } - errorMessage="Invalid regex" + name="Import" + isValid={(v) => parseTreeData(v) !== undefined} + initialValue="" + errorMessage="Invalid import" /> - - - + + + + + ); +} + +function EditSectionDialog({ + treeData, + name, + setGroup, +}: { + treeData: Group[]; + name: string; + setGroup: (name: string, newName: string, regex: string) => void; +}) { + const [open, setOpen] = useState(false); + + function isGoodName(value: string) { + return value == name || !isDupName(treeData, value); + } + + return ( + <> + + + { + e.preventDefault(); + const formData = new FormData(e.currentTarget); + const regex = formData.get("Filter") as string; + const newName = formData.get("Section name") as string; + if (!validRegex(regex) || !isGoodName(newName)) { + return; + } + setGroup(name, newName, regex); + setOpen(false); + }} + > + + node.name === name)?.regex.source ?? "" + } + errorMessage="Invalid regex" + /> + + + + + ); } @@ -133,57 +236,56 @@ function ResetButton({ onConfirm }: { onConfirm: () => void }) { return ( <> - - setOpen(false)} - aria-modal - > - - - - Are you sure you want to reset to default group settings? - - - - - + + + + + Are you sure you want to reset to default group settings? + + + + - - + + ); } +// MARK: Main Component + export default function SettingsModal({ + repositoryFullName, + branchName, visible, handleClose, }: { + repositoryFullName: string; + branchName: string; visible: boolean; handleClose: () => void; }) { - const [treeData, setTreeData] = useState(getStoredTreeData()); + const getStoredTreeDataCustom = () => { + return getStoredTreeData(repositoryFullName, branchName); + }; + const saveTreeDataCustom = (treeData: Group[]) => { + saveTreeData(repositoryFullName, branchName, treeData); + }; + const [treeData, setTreeData] = useState(getStoredTreeDataCustom()); const [orderBy, setOrderBy] = useState<"display" | "filter">("display"); const treeDataOrdered = treeData.sort((a, b) => { if (orderBy === "display") { @@ -343,7 +445,7 @@ export default function SettingsModal({ fullWidth={true} maxWidth="xl" onClose={() => { - saveTreeData(treeData); + saveTreeDataCustom(treeData); handleClose(); }} onClick={(e) => e.stopPropagation()} @@ -358,7 +460,7 @@ export default function SettingsModal({ { - saveTreeData(getDefaultGroupSettings()); + saveTreeDataCustom(getDefaultGroupSettings()); setTreeData(getDefaultGroupSettings()); }} /> @@ -377,9 +479,10 @@ export default function SettingsModal({ > Ordering by {orderBy} precedence + diff --git a/torchci/components/HudGroupingSettings/hudGroupingSettings.ts b/torchci/components/HudGroupingSettings/hudGroupingSettings.ts index 83735de5f3..9ed74f6062 100644 --- a/torchci/components/HudGroupingSettings/hudGroupingSettings.ts +++ b/torchci/components/HudGroupingSettings/hudGroupingSettings.ts @@ -9,8 +9,10 @@ import { } from "./defaults"; import { getStoredTreeData } from "./mainPageSettingsUtils"; -function getGroupSettings() { - const groups = getStoredTreeData() ?? getDefaultGroupSettings(); +function getGroupSettings(repositoryFullName: string, branchName: string) { + const groups = + getStoredTreeData(repositoryFullName, branchName) ?? + getDefaultGroupSettings(); groups.push({ name: GROUP_OTHER, regex: /.*/, @@ -22,13 +24,15 @@ function getGroupSettings() { } export function getGroupingData( + repositoryFullName: string, + branchName: string, shaGrid: RowData[], jobNames: Set, showUnstableGroup: boolean, unstableIssues?: IssueData[] ) { // Construct Job Groupping Mapping - const groupSettings = getGroupSettings(); + const groupSettings = getGroupSettings(repositoryFullName, branchName); const groupNameMapping = new Map>(); // group -> [job names] diff --git a/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts index a369caf3a0..68f2eff67d 100644 --- a/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts +++ b/torchci/components/HudGroupingSettings/mainPageSettingsUtils.ts @@ -9,7 +9,41 @@ export type Group = { persistent: boolean; }; -export function saveTreeData(treeData: Group[]) { +export function serializeTreeData(treeData: Group[]): string { + // Convert RegExp objects to a format that can be seralized + const serializable = treeData.map((group) => ({ + ...group, + regex: group.regex.source, // Store only the regex pattern as a string + })); + + return JSON.stringify(serializable); +} + +export function parseTreeData(input: string): Group[] | undefined { + try { + const parsed = JSON.parse(input); + + // Convert the stored string patterns back to RegExp objects + return parsed.map((item: any) => ({ + ...item, + regex: new RegExp(item.regex || ""), + })); + } catch (error) { + return undefined; + } +} + +const LOCAL_STORAGE_KEY = "hud_group_settings"; +export function saveTreeData( + repositoryFullName: string, + branchName: string, + treeData: Group[] +) { + const localStorageContents = JSON.parse( + localStorage.getItem(LOCAL_STORAGE_KEY) || "{}" + ); + const repoBranchKey = `${repositoryFullName}::${branchName}`; + if ( _.isEqual( treeData.sort((a, b) => a.name.localeCompare(b.name)), @@ -18,37 +52,51 @@ export function saveTreeData(treeData: Group[]) { ) { // If the current settings are the same as the default, remove from // localStorage - localStorage.removeItem("hud_group_settings"); + localStorageContents[repoBranchKey] = undefined; + localStorage.setItem( + LOCAL_STORAGE_KEY, + JSON.stringify(localStorageContents) + ); return; } - // Convert RegExp objects to a format that can be seralized - const serializable = treeData.map((group) => ({ - ...group, - regex: group.regex.source, // Store only the regex pattern as a string - })); - - const setting = JSON.stringify(serializable); - localStorage.setItem("hud_group_settings", setting); + const setting = serializeTreeData(treeData); + localStorageContents[repoBranchKey] = setting; + localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(localStorageContents)); } -export function getStoredTreeData(): Group[] { +export function getStoredTreeData( + repositoryFullName: string, + branchName: string +): Group[] { try { // Try to load saved tree data from localStorage - const stored = localStorage.getItem("hud_group_settings"); + const stored = JSON.parse( + localStorage.getItem("hud_group_settings") || "{}" + ); if (!stored) return getDefaultGroupSettings(); - - const parsed = JSON.parse(stored); - - // Convert the stored string patterns back to RegExp objects - return parsed.map((item: any) => ({ - ...item, - regex: new RegExp(item.regex || ""), - })); + const repoBranchKey = `${repositoryFullName}::${branchName}`; + const storedSetting = stored[repoBranchKey]; + if (storedSetting) { + const parsed = parseTreeData(storedSetting); + if (parsed === undefined) { + return getDefaultGroupSettings(); + } + return parsed; + } + const backUpKey = `${repositoryFullName}::main`; + const backUpSetting = stored[backUpKey]; + if (backUpSetting) { + const parsed = parseTreeData(backUpSetting); + if (parsed !== undefined) { + return parsed; + } + } + return getDefaultGroupSettings(); } catch (error) { console.error("Error loading stored group settings:", error); - return []; + return getDefaultGroupSettings(); } } diff --git a/torchci/components/common/ValidatedTextField.tsx b/torchci/components/common/ValidatedTextField.tsx index c20a0f25dc..06bd3f8211 100644 --- a/torchci/components/common/ValidatedTextField.tsx +++ b/torchci/components/common/ValidatedTextField.tsx @@ -28,6 +28,7 @@ export function ValidatedTextField({ onChange={onChangeWrapper} error={!valid} helperText={!valid ? errorMessage : ""} + name={name} /> ); } diff --git a/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx b/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx index a4ed9e0b80..6e28e46d48 100644 --- a/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx +++ b/torchci/pages/hud/[repoOwner]/[repoName]/[branch]/[[...page]].tsx @@ -346,6 +346,8 @@ function FiltersAndSettings({}: {}) { /> setGroupingSettingsOpen(false)} /> @@ -573,6 +575,8 @@ function GroupedHudTable({ params }: { params: HudParams }) { groupsWithFailures, groupSettings, } = getGroupingData( + `${params.repoOwner}/${params.repoName}`, + params.branch, data ?? [], jobNames, (!useGrouping && hideUnstable) || (useGrouping && !hideUnstable), From 5c07b9c2a9d1ee79f1578461bc4f7982882db303 Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Fri, 19 Sep 2025 15:03:34 -0700 Subject: [PATCH 11/12] tc --- torchci/components/HudGroupingSettings/MainPageSettings.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.tsx b/torchci/components/HudGroupingSettings/MainPageSettings.tsx index 554d3904ba..5f07884903 100644 --- a/torchci/components/HudGroupingSettings/MainPageSettings.tsx +++ b/torchci/components/HudGroupingSettings/MainPageSettings.tsx @@ -296,6 +296,10 @@ export default function SettingsModal({ const sensors = useSensors(useSensor(PointerSensor)); + React.useEffect(() => { + setTreeData(getStoredTreeDataCustom()); + }, [repositoryFullName, branchName]); + function addSection() { setTreeData([ { From 563d45972ec32a96f95506c1ba5fce592228d780 Mon Sep 17 00:00:00 2001 From: Catherine Lee Date: Fri, 19 Sep 2025 15:35:50 -0700 Subject: [PATCH 12/12] tc --- .../HudGroupingSettings/MainPageSettings.tsx | 21 +++++++++++++++++++ .../HudGroupingSettings/defaults.ts | 1 + .../hudGroupingSettings.ts | 8 +++++++ .../mainPageSettingsUtils.ts | 1 + 4 files changed, 31 insertions(+) diff --git a/torchci/components/HudGroupingSettings/MainPageSettings.tsx b/torchci/components/HudGroupingSettings/MainPageSettings.tsx index 5f07884903..889c755df5 100644 --- a/torchci/components/HudGroupingSettings/MainPageSettings.tsx +++ b/torchci/components/HudGroupingSettings/MainPageSettings.tsx @@ -308,6 +308,7 @@ export default function SettingsModal({ filterPriority: 0, displayPriority: 0, persistent: false, + hide: false, }, ...treeData.map((node) => { return { @@ -363,6 +364,7 @@ export default function SettingsModal({ const style = { transform: CSS.Transform.toString(transform), transition, + opacity: data.hide ? 0.4 : 1, }; return ( @@ -382,6 +384,25 @@ export default function SettingsModal({ {data.regex.source} + +