Skip to content

Commit

Permalink
Add drop-down menu to load a previous config (#586)
Browse files Browse the repository at this point in the history
  • Loading branch information
JosephMarinier committed Jun 28, 2023
2 parents ce706e7 + c666265 commit 05256e2
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Released changes are shown in the

### Added
- Two buttons in the config UI to import/export a JSON config file.
- Drop-down menu to load a previous config.
- For HF pipelines, the saliency layer is now automatically detected and no longer needs to be specified in the config. To disable saliency maps, the user can still specify `null`.

### Changed
Expand Down
19 changes: 19 additions & 0 deletions webapp/src/components/HashChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Chip, Tooltip } from "@mui/material";
import React from "react";

const HashChip: React.FC<{ hash: string }> = ({ hash }) => (
<Tooltip title="This hash of the config lets you see when the same config appears multiple times.">
<Chip
size="small"
label={hash}
sx={(theme) => ({
backgroundColor: `#${hash}`,
color: theme.palette.getContrastText(`#${hash}`),
cursor: "unset",
fontFamily: "Monospace",
})}
/>
</Tooltip>
);

export default React.memo(HashChip);
66 changes: 65 additions & 1 deletion webapp/src/pages/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Close, Download, Upload, Warning } from "@mui/icons-material";
import { Close, Download, History, Upload, Warning } from "@mui/icons-material";
import {
Box,
Button,
Expand All @@ -18,11 +18,14 @@ import {
InputBaseComponentProps,
inputClasses,
inputLabelClasses,
Menu,
MenuItem,
Typography,
} from "@mui/material";
import noData from "assets/void.svg";
import AccordionLayout from "components/AccordionLayout";
import FileInputButton from "components/FileInputButton";
import HashChip from "components/HashChip";
import Loading from "components/Loading";
import AutocompleteStringField from "components/Settings/AutocompleteStringField";
import CustomObjectFields from "components/Settings/CustomObjectFields";
Expand All @@ -36,6 +39,7 @@ import React from "react";
import { useParams } from "react-router-dom";
import {
getConfigEndpoint,
getConfigHistoryEndpoint,
getDefaultConfigEndpoint,
updateConfigEndpoint,
validateConfigEndpoint,
Expand All @@ -53,6 +57,7 @@ import {
import { PickByValue } from "types/models";
import { downloadBlob } from "utils/api";
import { UNKNOWN_ERROR } from "utils/const";
import { formatDateISO } from "utils/format";
import { raiseErrorToast } from "utils/helpers";

type MetricState = MetricDefinition & { name: string };
Expand Down Expand Up @@ -223,6 +228,11 @@ const Settings: React.FC<Props> = ({ open, onClose }) => {
language: language ?? resultingConfig.language,
});

const { data: configHistory } = getConfigHistoryEndpoint.useQuery({ jobId });

const [configHistoryAnchor, setConfigHistoryAnchor] =
React.useState<null | HTMLElement>(null);

const updatePartialConfig = React.useCallback(
(update: Partial<ConfigState>) =>
setPartialConfig((partialConfig) => ({ ...partialConfig, ...update })),
Expand Down Expand Up @@ -272,6 +282,24 @@ const Settings: React.FC<Props> = ({ open, onClose }) => {
metricsNames.has("") ||
resultingConfig.metrics.some(({ class_name }) => class_name.trim() === "");

const fullHashCount = new Set(configHistory?.map(({ hash }) => hash)).size;
const hashCount = new Set(configHistory?.map(({ hash }) => hash.slice(0, 3)))
.size;
const nameCount = new Set(configHistory?.map(({ config }) => config.name))
.size;

// Don't show the hash (hashSize = null) if no two different configs have the same name.
// Show a 6-char hash if there is a collision in the first 3 chars.
// Otherwise, show a 3-char hash.
// Probability of a hash collision with the 3-char hash:
// 10 different configs: 1 %
// 30 different configs: 10 %
// 76 different configs: 50 %
// With the 6-char hash:
// 581 different configs: 1 %
const hashChars =
nameCount === fullHashCount ? null : hashCount === fullHashCount ? 3 : 6;

const handleFileRead = (text: string) => {
try {
const body = JSON.parse(text);
Expand Down Expand Up @@ -307,6 +335,42 @@ const Settings: React.FC<Props> = ({ open, onClose }) => {
<Typography variant="inherit" flex={1}>
Configuration
</Typography>
{configHistory?.length && (
<>
<Button
disabled={areInputsDisabled}
startIcon={<History />}
onClick={(event) => setConfigHistoryAnchor(event.currentTarget)}
>
Load previous config
</Button>
<Menu
anchorEl={configHistoryAnchor}
open={Boolean(configHistoryAnchor)}
onClick={() => setConfigHistoryAnchor(null)}
>
{configHistory.map(({ config, created_on, hash }, index) => (
<MenuItem
key={index}
sx={{ gap: 2 }}
onClick={() => {
setConfigHistoryAnchor(null);
setPartialConfig(azimuthConfigToConfigState(config));
}}
>
<Typography flex={1}>{config.name}</Typography>
{hashChars && <HashChip hash={hash.slice(0, hashChars)} />}
<Typography
variant="body2"
sx={{ fontFamily: "Monospace" }}
>
{formatDateISO(new Date(created_on))}
</Typography>
</MenuItem>
))}
</Menu>
</>
)}
<FileInputButton
accept=".json"
disabled={areInputsDisabled}
Expand Down
9 changes: 9 additions & 0 deletions webapp/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const tagTypes = [
"DatasetInfo",
"ConfidenceHistogram",
"Config",
"ConfigHistory",
"DefaultConfig",
"Metrics",
"OutcomeCountPerThreshold",
Expand Down Expand Up @@ -272,6 +273,13 @@ export const api = createApi({
"Something went wrong fetching the config"
),
}),
getConfigHistory: build.query({
providesTags: [{ type: "ConfigHistory" }],
queryFn: responseToData(
fetchApi({ path: "/config/history", method: "get" }),
"Something went wrong fetching the config history"
),
}),
getDefaultConfig: build.query({
providesTags: [{ type: "DefaultConfig" }],
queryFn: responseToData(
Expand Down Expand Up @@ -371,6 +379,7 @@ export const api = createApi({
export const {
getConfidenceHistogram: getConfidenceHistogramEndpoint,
getConfig: getConfigEndpoint,
getConfigHistory: getConfigHistoryEndpoint,
getDefaultConfig: getDefaultConfigEndpoint,
getConfusionMatrix: getConfusionMatrixEndpoint,
getDatasetInfo: getDatasetInfoEndpoint,
Expand Down
6 changes: 6 additions & 0 deletions webapp/src/utils/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export const formatRatioAsPercentageString = (
digits: number = 2
) => `${formatNumberAsString(100 * value, digits)}%`;

const pad = (n: number) => String(n).padStart(2, "0");

export const formatDateISO = (date: Date) =>
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;

export const camelToTitleCase = (camelCase: string) =>
// Safari and iOS browsers don't support lookbehind in regular expressions.
camelCase.replace(/([a-z])(?=[A-Z0-9])|([A-Z0-9])(?=[A-Z][a-z])/g, "$& ");

0 comments on commit 05256e2

Please sign in to comment.