Skip to content

Commit

Permalink
Refactor Config
Browse files Browse the repository at this point in the history
- Change from recursive config inputs so all inputs don't remount on input change ( This is particularly brutal on text fields )
- Power everything directly from the server config which is pulled at a interval to keep things fresh. Now changes happening well you edit are reflected almost instantly in your display.
- Simplify the object inputs to use the same model. This adds some rendering overhead for a lot of code reduction.
  • Loading branch information
CannonLock committed Aug 8, 2024
1 parent 3c65a6b commit e39a75e
Show file tree
Hide file tree
Showing 51 changed files with 1,864 additions and 1,460 deletions.
3 changes: 1 addition & 2 deletions web_ui/frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ as they would in production.

```shell
# From repo root
make web-build
goreleaser --clean --snapshot
make pelican-build
docker run --rm -it -p 8444:8444 -w /app -v $PWD/dist/pelican_linux_arm64/:/app -v $PWD/local/:/etc/pelican/ hub.opensciencegrid.org/pelican_platform/pelican-dev:latest-itb /bin/bash
```

Expand Down
149 changes: 87 additions & 62 deletions web_ui/frontend/app/config/components/ConfigDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,108 @@
import { Config, ParameterInputProps } from '@/components/Config/index';
import React, { memo } from 'react';
import { Box, Button, Typography } from '@mui/material';
import { Field } from '@/components/Config';
import { QuestionMark } from '@mui/icons-material';
import { OverridableStringUnion } from '@mui/types';
import { Variant } from '@mui/material/styles/createTypography';
import { TypographyPropsVariantOverrides } from '@mui/material/Typography';
import React from 'react';
import { isConfig, sortConfig } from '@/app/config/util';

import {
Config,
Field as NonMemoizedField,
ParameterMetadata,
ParameterMetadataRecord,
ParameterValue,
ParameterMetadataList,
ParameterValueRecord,
} from '@/components/configuration';
import { isEqual } from 'lodash';

// Memoize Expensive Components
const Field = memo(NonMemoizedField);

export interface ConfigDisplayProps {
id: string[];
name: string;
value: Config | ParameterInputProps;
level: number;
config?: ParameterValueRecord;
patch: ParameterValueRecord;
metadata: ParameterMetadataRecord;
onChange: (patch: any) => void;
}

export function ConfigDisplay({
id,
name,
value,
level = 1,
config,
metadata,
patch,
onChange,
}: ConfigDisplayProps) {
if (name != '') {
id = [...id, name];
}
const existingLabels = new Set<string>();

// If this is a ConfigValue then display it
if (!isConfig(value)) {
return (
<Box pt={2} display={'flex'} id={id.join('-')}>
<Box flexGrow={1} minWidth={0}>
<Field {...(value as ParameterInputProps)} onChange={onChange} />
</Box>
return (
<>
{Object.entries(metadata).map(([name, parameterMetadata]) => {
let label = null;
let groupName = name.split('.').slice(0, -1).join('.');

<Button
size={'small'}
href={`https://docs.pelicanplatform.org/parameters#${id.join('-')}`}
target={'_blank'}
>
<QuestionMark />
</Button>
</Box>
);
}
if (!existingLabels.has(groupName)) {
existingLabels.add(groupName);
label = <ConfigCategoryLabel name={groupName} />;
}

// If this is a Config then display all of its values
let subValues = Object.entries(value);
subValues.sort(sortConfig);
return (
<Box key={name}>
{label}
<ConfigField
metadata={parameterMetadata}
value={name in patch ? patch[name] : config?.[name]}
focused={name in patch && !isEqual(patch[name], config?.[name])}
onChange={onChange}
/>
</Box>
);
})}
</>
);
}

let configDisplays = subValues.map(([k, v]) => {
return (
<ConfigDisplay
id={id}
key={k}
name={k}
value={v}
level={level + 1}
onChange={onChange}
/>
);
});
interface ConfigFieldProps {
metadata: ParameterMetadata;
value: ParameterValue;
onChange: (patch: any) => void;
focused: boolean;
}

export const ConfigField = ({
metadata,
value,
onChange,
focused,
}: ConfigFieldProps) => {
return (
<Box pt={2} display={'flex'} id={metadata.name.split('.').join('-')}>
<Box flexGrow={1} minWidth={0}>
<Field
{...(metadata as ParameterMetadata)}
value={value}
onChange={onChange}
focused={focused}
/>
</Box>
<Button
size={'small'}
href={`https://docs.pelicanplatform.org/parameters#${metadata.name.split('.').join('-')}`}
target={'_blank'}
>
<QuestionMark />
</Button>
</Box>
);
};

export const ConfigCategoryLabel = ({ name }: { name: string }) => {
const splitName = name.split('.');

let variant: OverridableStringUnion<
'inherit' | Variant,
TypographyPropsVariantOverrides
>;
switch (level) {
switch (splitName.length) {
case 1:
variant = 'h1';
break;
Expand All @@ -91,20 +126,10 @@ export function ConfigDisplay({
}

return (
<>
{name ? (
<Typography
id={id.join('-')}
variant={variant}
component={variant}
mt={2}
>
{name}
</Typography>
) : undefined}
{configDisplays}
</>
<Typography id={splitName.join('-')} component={variant} mt={2}>
{splitName.pop()}
</Typography>
);
}
};

export default ConfigDisplay;
75 changes: 44 additions & 31 deletions web_ui/frontend/app/config/components/TableOfContents.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,58 @@
import { Config, ParameterInputProps } from '@/components/Config/index';
import React, { useState } from 'react';
import {
Config,
ParameterInputProps,
ParameterMetadata,
ParameterMetadataRecord,
} from '@/components/configuration/index';
import React, { useMemo, useState } from 'react';
import { Box, Link, Typography } from '@mui/material';
import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material';
import { isConfig, sortConfig } from '@/app/config/util';
import {
ExpandedObject,
expandObject,
isParameterMetadata,
sortMetadata,
} from '@/app/config/util';

interface TableOfContentsProps {
id: string[];
name: string;
value: Config | ParameterInputProps;
level: number;
export interface TableOfContentsProps {
metadata: ParameterMetadataRecord;
}

export function TableOfContents({
id,
export const TableOfContents = ({ metadata }: TableOfContentsProps) => {
const expandedMetadata = expandObject(metadata);
return <TableOfContentsHelper name={[]} metadata={expandedMetadata} />;
};

interface TableOfContentsHelperProps {
name: string[];
metadata: ExpandedObject<ParameterMetadata> | ParameterMetadata;
}

export function TableOfContentsHelper({
name,
value,
level = 1,
}: TableOfContentsProps) {
metadata,
}: TableOfContentsHelperProps) {
const [open, setOpen] = useState(false);

if (name != '') {
id = [...id, name];
}

let subContents = undefined;
if (isConfig(value)) {
let subValues = Object.entries(value);
subValues.sort(sortConfig);
subContents = subValues.map(([key, value]) => {
// If we arrived at a leaf containing the metadata for a parameter, display the field
if (!isParameterMetadata(metadata)) {
let subValues = Object.entries(metadata as ParameterMetadataRecord);
subValues.sort(sortMetadata);
subContents = subValues.map(([key, metadata]) => {
const childName = [...name, key];
return (
<TableOfContents
id={id}
key={key}
name={key}
value={value}
level={level + 1}
/>
<TableOfContentsHelper key={key} name={childName} metadata={metadata} />
);
});
}

// Check if this is the root element, if so we want to return the children directly
if (name.length == 0) {
return subContents;
}

const level = name.length;
let headerPointer = (
<Box
sx={{
Expand All @@ -52,7 +65,7 @@ export function TableOfContents({
}}
>
<Link
href={subContents ? undefined : `#${id.join('-')}`}
href={subContents ? undefined : `#${name.join('-')}`}
sx={{
cursor: 'pointer',
textDecoration: 'none',
Expand All @@ -71,7 +84,7 @@ export function TableOfContents({
fontWeight: subContents ? '600' : 'normal',
}}
>
{name}
{name[name.length - 1]}
</Typography>
{subContents ? open ? <ArrowDropUp /> : <ArrowDropDown /> : undefined}
</Link>
Expand All @@ -81,7 +94,7 @@ export function TableOfContents({
return (
<>
{name ? headerPointer : undefined}
{subContents && level != 1 ? (
{subContents ? (
<Box
sx={{
display: open ? 'block' : 'none',
Expand Down
Loading

0 comments on commit e39a75e

Please sign in to comment.