Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ProjectLabel.names read-only for ML-generated labels #232

Merged
merged 4 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/api/buildQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const projectLabelFields = `
name
color
reviewerEnabled
ml
`;

const projectFields = `
Expand Down
1 change: 1 addition & 0 deletions src/components/Modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const ModalBody = styled('div', {
minHeight: '$7',
maxHeight: 'calc(95vh - $7)',
overflowY: 'scroll',
overflowX: 'hidden',
position: 'relative',
});

Expand Down
51 changes: 29 additions & 22 deletions src/features/projects/AddAutomationRuleForm.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { styled } from '../../theme/stitches.config.js';
import _ from 'lodash';
import { Formik, Form, Field, FieldArray, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import { updateAutomationRules, selectMLModels, fetchModels } from './projectsSlice.js';
import SelectField from '../../components/SelectField.jsx';
import Button from '../../components/Button.jsx';
import { FormWrapper, FieldRow, ButtonRow, FormFieldWrapper, FormError } from '../../components/Form.jsx';
import {
FormWrapper,
FieldRow,
ButtonRow,
FormFieldWrapper,
FormError,
} from '../../components/Form.jsx';
import CategoryConfigForm from './CategoryConfigForm.jsx';

const CategoryConfigSection = styled('div', {
maxHeight: '220px',
overflowY: 'scroll',
});

const emptyRule = {
name: '',
event: {
Expand Down Expand Up @@ -145,7 +145,11 @@ const AddAutomationRuleForm = ({ project, availableModels, hideAddRuleForm, rule
{values.event.type.value === 'label-added' && (
<FormFieldWrapper css={{ flexGrow: '0' }}>
<label htmlFor="event-label">Label</label>
<Field id="event-label" name="event.label" value={values.event.label ? values.event.label : ''} />
<Field
id="event-label"
name="event.label"
value={values.event.label ? values.event.label : ''}
/>
<ErrorMessage component={FormError} name="event.label" />
</FormFieldWrapper>
)}
Expand All @@ -157,6 +161,7 @@ const AddAutomationRuleForm = ({ project, availableModels, hideAddRuleForm, rule
<SelectField
name="action.type"
label="Action"
menuPlacement="top"
value={values.action.type}
onChange={(name, value) => {
setFieldValue(name, value);
Expand All @@ -181,6 +186,7 @@ const AddAutomationRuleForm = ({ project, availableModels, hideAddRuleForm, rule
<SelectField
name="action.model"
label="Model"
menuPlacement="top"
value={values.action.model}
onChange={(name, value) => {
setFieldValue(name, value);
Expand Down Expand Up @@ -220,20 +226,21 @@ const AddAutomationRuleForm = ({ project, availableModels, hideAddRuleForm, rule
</FieldRow>

{/* category configurations */}
{values.action.categoryConfig && Object.entries(values.action.categoryConfig).length > 0 && (
<CategoryConfigSection>
<label>Confidence thresholds</label>
<FieldArray name="categoryConfigs">
<>
{Object.entries(values.action.categoryConfig)
.filter(([k]) => k !== 'empty') // NOTE: manually hiding "empty" categories b/c it isn't a real category returned by MDv5
.map(([k, v]) => (
<CategoryConfigForm key={k} catName={k} config={v} />
))}
</>
</FieldArray>
</CategoryConfigSection>
)}
{values.action.categoryConfig &&
Object.entries(values.action.categoryConfig).length > 0 && (
<div>
<label>Confidence thresholds</label>
<FieldArray name="categoryConfigs">
<>
{Object.entries(values.action.categoryConfig)
.filter(([k]) => k !== 'empty') // NOTE: manually hiding "empty" categories b/c it isn't a real category returned by MDv5
.map(([k, v]) => (
<CategoryConfigForm key={k} catName={k} config={v} />
))}
</>
</FieldArray>
</div>
)}

<ButtonRow>
<Button type="button" size="large" onClick={handleDiscardRuleClick}>
Expand Down
7 changes: 4 additions & 3 deletions src/features/projects/ManageLabelsModal/EditLabelForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ const DisabledIndicator = styled('span', {
});

const EditLabelForm = ({ label, labels, setLabelToDelete, setAlertOpen }) => {
const { _id, name, color, reviewerEnabled } = label;
const { _id, name, color, reviewerEnabled, ml } = label;
const dispatch = useDispatch();
const [showForm, setShowForm] = useState(false);

const toggleOpenForm = useCallback(() => setShowForm((prev) => !prev), []);
const onSubmit = useCallback((values) => {
const onSubmit = useCallback((values, { resetForm }) => {
dispatch(updateProjectLabel(values));
setShowForm(false);
resetForm();
}, []);

const deleteLabel = useCallback((values) => {
Expand Down Expand Up @@ -59,7 +60,7 @@ const EditLabelForm = ({ label, labels, setLabelToDelete, setAlertOpen }) => {
return (
<Formik
enableReinitialize
initialValues={{ _id, name, color, reviewerEnabled }}
initialValues={{ _id, name, color, reviewerEnabled, ml }}
validationSchema={schema(name)}
onSubmit={onSubmit}
>
Expand Down
60 changes: 53 additions & 7 deletions src/features/projects/ManageLabelsModal/LabelForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import { Form, Field, useFormikContext } from 'formik';
import Button from '../../../components/Button';
import IconButton from '../../../components/IconButton.jsx';
import InfoIcon from '../../../components/InfoIcon';
import { SymbolIcon } from '@radix-ui/react-icons';
import { LockClosedIcon, SymbolIcon } from '@radix-ui/react-icons';
import { SwitchRoot, SwitchThumb } from '../../../components/Switch.jsx';
import { Tooltip, TooltipContent, TooltipArrow, TooltipTrigger } from '../../../components/Tooltip.jsx';
import {
Tooltip,
TooltipContent,
TooltipArrow,
TooltipTrigger,
} from '../../../components/Tooltip.jsx';
import { FormWrapper, FormFieldWrapper, FormError } from '../../../components/Form';
import { FormRow, FormButtons, ColorPicker } from './components';
import { getRandomColor, getTextColor } from '../../../app/utils.js';
Expand All @@ -18,13 +23,14 @@ const LabelForm = ({ onCancel }) => {
<FormWrapper>
<Form>
<FormRow>
<FormFieldWrapper>
<FormFieldWrapper css={{ position: 'relative' }}>
<label htmlFor="name">Name</label>
<Field name="name" id="name" />
<Field name="name" id="name" disabled={values.ml} />
{values.ml && <LabelLockOverlay />}
{!!errors.name && touched.name && <FormError>{errors.name}</FormError>}
</FormFieldWrapper>
<FormFieldWrapper>
<label htmlFor="name">Color</label>
<label htmlFor="color">Color</label>
<ColorPicker>
<Tooltip>
<TooltipTrigger asChild>
Expand Down Expand Up @@ -152,9 +158,49 @@ const ColorSwatch = styled('button', {

const ReviewerEnabledHelp = () => (
<div style={{ maxWidth: '200px' }}>
Disabling a label will prevent users from applying it to images going forward, but it will not remove existing
instances of the label on your images.
Disabling a label will prevent users from applying it to images going forward, but it will not
remove existing instances of the label on your images.
</div>
);

const LockIcon = styled(LockClosedIcon, {
marginLeft: '$2',
marginRight: '$2',
});

const Overlay = styled('div', {
position: 'absolute',
color: '$textMedium',
height: '53px',
width: '176px',
top: '30px',
left: '1px',
padding: '$1',
borderRadius: '$1',
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
});

const LabelLockOverlay = () => (
<Tooltip>
<TooltipTrigger asChild>
<Overlay>
<LockIcon />
</Overlay>
</TooltipTrigger>
<TooltipContent
side="top"
sideOffset={5}
css={{
maxWidth: 324,
}}
>
You can&apos;t edit this label&apos;s name because it is managed by a machine learning model,
but you can change it&apos;s color and enable/disable it.
<TooltipArrow />
</TooltipContent>
</Tooltip>
);

export default LabelForm;
Loading