mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Task/filter addon by project and environment (#1133)
* feat: add project and environments filters for addons Co-authored-by: Simon Hornby <liquidwicked64@gmail.com>
This commit is contained in:
parent
235bf428fe
commit
4c5eb20e09
@ -1,45 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Grid, FormControlLabel, Checkbox } from '@mui/material';
|
|
||||||
|
|
||||||
import { styles as themeStyles } from 'component/common';
|
|
||||||
import { IAddonProvider } from 'interfaces/addons';
|
|
||||||
|
|
||||||
interface IAddonProps {
|
|
||||||
provider?: IAddonProvider;
|
|
||||||
checkedEvents: string[];
|
|
||||||
setEventValue: (
|
|
||||||
name: string
|
|
||||||
) => (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AddonEvents = ({
|
|
||||||
provider,
|
|
||||||
checkedEvents,
|
|
||||||
setEventValue,
|
|
||||||
error,
|
|
||||||
}: IAddonProps) => {
|
|
||||||
if (!provider) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<h4>Events</h4>
|
|
||||||
<span className={themeStyles.error}>{error}</span>
|
|
||||||
<Grid container spacing={0}>
|
|
||||||
{provider.events.map(e => (
|
|
||||||
<Grid item xs={4} key={e}>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={checkedEvents.includes(e)}
|
|
||||||
onChange={setEventValue(e)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={e}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,19 +1,17 @@
|
|||||||
import {
|
import React, {
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
ChangeEvent,
|
|
||||||
VFC,
|
|
||||||
ChangeEventHandler,
|
ChangeEventHandler,
|
||||||
FormEventHandler,
|
FormEventHandler,
|
||||||
MouseEventHandler,
|
MouseEventHandler,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
VFC,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { TextField, FormControlLabel, Switch, Button } from '@mui/material';
|
import { Button, FormControlLabel, Switch, TextField } from '@mui/material';
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
import { styles as themeStyles } from 'component/common';
|
import { styles as themeStyles } from 'component/common';
|
||||||
import { trim } from 'component/common/util';
|
import { trim } from 'component/common/util';
|
||||||
import { IAddon, IAddonProvider } from 'interfaces/addons';
|
import { IAddon, IAddonProvider } from 'interfaces/addons';
|
||||||
import { AddonParameters } from './AddonParameters/AddonParameters';
|
import { AddonParameters } from './AddonParameters/AddonParameters';
|
||||||
import { AddonEvents } from './AddonEvents/AddonEvents';
|
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@ -21,6 +19,9 @@ import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
|
|||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { makeStyles } from 'tss-react/mui';
|
import { makeStyles } from 'tss-react/mui';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import useProjects from '../../../hooks/api/getters/useProjects/useProjects';
|
||||||
|
import { useEnvironments } from '../../../hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
|
import { AddonMultiSelector } from './AddonMultiSelector/AddonMultiSelector';
|
||||||
|
|
||||||
const useStyles = makeStyles()(theme => ({
|
const useStyles = makeStyles()(theme => ({
|
||||||
nameInput: {
|
nameInput: {
|
||||||
@ -52,12 +53,27 @@ export const AddonForm: VFC<IAddonFormProps> = ({
|
|||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
|
const { projects: availableProjects } = useProjects();
|
||||||
|
const selectableProjects = availableProjects.map(project => ({
|
||||||
|
value: project.id,
|
||||||
|
label: project.name,
|
||||||
|
}));
|
||||||
|
const { environments: availableEnvironments } = useEnvironments();
|
||||||
|
const selectableEnvironments = availableEnvironments.map(environment => ({
|
||||||
|
value: environment.name,
|
||||||
|
label: environment.name,
|
||||||
|
}));
|
||||||
|
const selectableEvents = provider?.events.map(event => ({
|
||||||
|
value: event,
|
||||||
|
label: event,
|
||||||
|
}));
|
||||||
const [formValues, setFormValues] = useState(initialValues);
|
const [formValues, setFormValues] = useState(initialValues);
|
||||||
const [errors, setErrors] = useState<{
|
const [errors, setErrors] = useState<{
|
||||||
containsErrors: boolean;
|
containsErrors: boolean;
|
||||||
parameters: Record<string, string>;
|
parameters: Record<string, string>;
|
||||||
events?: string;
|
events?: string;
|
||||||
|
projects?: string;
|
||||||
|
environments?: string;
|
||||||
general?: string;
|
general?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}>({
|
}>({
|
||||||
@ -106,22 +122,39 @@ export const AddonForm: VFC<IAddonFormProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setEventValue =
|
const setEventValues = (events: string[]) => {
|
||||||
(name: string) => (event: ChangeEvent<HTMLInputElement>) => {
|
setFormValues(
|
||||||
setFormValues(
|
produce(draft => {
|
||||||
produce(draft => {
|
draft.events = events;
|
||||||
if (event.target.checked) {
|
})
|
||||||
draft.events.push(name);
|
);
|
||||||
} else {
|
setErrors(prev => ({
|
||||||
draft.events = draft.events.filter(e => e !== name);
|
...prev,
|
||||||
}
|
events: undefined,
|
||||||
})
|
}));
|
||||||
);
|
};
|
||||||
setErrors(prev => ({
|
const setProjects = (projects: string[]) => {
|
||||||
...prev,
|
setFormValues(
|
||||||
events: undefined,
|
produce(draft => {
|
||||||
}));
|
draft.projects = projects;
|
||||||
};
|
})
|
||||||
|
);
|
||||||
|
setErrors(prev => ({
|
||||||
|
...prev,
|
||||||
|
projects: undefined,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
const setEnvironments = (environments: string[]) => {
|
||||||
|
setFormValues(
|
||||||
|
produce(draft => {
|
||||||
|
draft.environments = environments;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setErrors(prev => ({
|
||||||
|
...prev,
|
||||||
|
environments: undefined,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
@ -234,12 +267,32 @@ export const AddonForm: VFC<IAddonFormProps> = ({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className={styles.formSection}>
|
<section className={styles.formSection}>
|
||||||
<AddonEvents
|
<AddonMultiSelector
|
||||||
provider={provider}
|
options={selectableEvents || []}
|
||||||
checkedEvents={formValues.events}
|
selectedItems={formValues.events}
|
||||||
setEventValue={setEventValue}
|
onChange={setEventValues}
|
||||||
error={errors.events}
|
entityName={'event'}
|
||||||
|
selectAllEnabled={false}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
<section className={styles.formSection}>
|
||||||
|
<AddonMultiSelector
|
||||||
|
options={selectableProjects}
|
||||||
|
selectedItems={formValues.projects || []}
|
||||||
|
onChange={setProjects}
|
||||||
|
entityName={'project'}
|
||||||
|
selectAllEnabled={true}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
<section className={styles.formSection}>
|
||||||
|
<AddonMultiSelector
|
||||||
|
options={selectableEnvironments}
|
||||||
|
selectedItems={formValues.environments || []}
|
||||||
|
onChange={setEnvironments}
|
||||||
|
entityName={'environment'}
|
||||||
|
selectAllEnabled={true}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<section className={styles.formSection}>
|
<section className={styles.formSection}>
|
||||||
|
@ -0,0 +1,143 @@
|
|||||||
|
import { vi } from 'vitest';
|
||||||
|
import React from 'react';
|
||||||
|
import { screen, waitFor, within } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { render } from 'utils/testRenderer';
|
||||||
|
import {
|
||||||
|
IAddonMultiSelectorProps,
|
||||||
|
AddonMultiSelector,
|
||||||
|
} from './AddonMultiSelector';
|
||||||
|
|
||||||
|
const onChange = vi.fn();
|
||||||
|
const onFocus = vi.fn();
|
||||||
|
|
||||||
|
const mockProps: IAddonMultiSelectorProps = {
|
||||||
|
options: [
|
||||||
|
{ label: 'Project1', value: 'project1' },
|
||||||
|
{ label: 'Project2', value: 'project2' },
|
||||||
|
{ label: 'Project3', value: 'project3' },
|
||||||
|
],
|
||||||
|
selectedItems: [],
|
||||||
|
onChange,
|
||||||
|
onFocus,
|
||||||
|
selectAllEnabled: true,
|
||||||
|
entityName: 'project',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('AddonMultiSelector', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
onChange.mockClear();
|
||||||
|
onFocus.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with default state', () => {
|
||||||
|
render(<AddonMultiSelector {...mockProps} selectedItems={['*']} />);
|
||||||
|
|
||||||
|
const checkbox = screen.getByLabelText(
|
||||||
|
/all current and future projects/i
|
||||||
|
);
|
||||||
|
expect(checkbox).toBeChecked();
|
||||||
|
|
||||||
|
const selectInputContainer = screen.getByTestId('select-project-input');
|
||||||
|
const input = within(selectInputContainer).getByRole('combobox');
|
||||||
|
expect(input).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can toggle "ALL" checkbox', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<AddonMultiSelector {...mockProps} selectedItems={['*']} />);
|
||||||
|
|
||||||
|
await user.click(screen.getByTestId('select-all-projects'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText(/all current and future projects/i)
|
||||||
|
).not.toBeChecked();
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('Projects')).toBeEnabled();
|
||||||
|
|
||||||
|
await user.click(screen.getByTestId('select-all-projects'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText(/all current and future projects/i)
|
||||||
|
).toBeChecked();
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('Projects')).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with autocomplete enabled if default value is not a wildcard', () => {
|
||||||
|
render(
|
||||||
|
<AddonMultiSelector {...mockProps} selectedItems={['project1']} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkbox = screen.getByLabelText(
|
||||||
|
/all current and future projects/i
|
||||||
|
);
|
||||||
|
expect(checkbox).not.toBeChecked();
|
||||||
|
|
||||||
|
const selectInputContainer = screen.getByTestId('select-project-input');
|
||||||
|
const input = within(selectInputContainer).getByRole('combobox');
|
||||||
|
expect(input).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Select/Deselect projects in dropdown', () => {
|
||||||
|
it("doesn't show up for less than 3 options", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<AddonMultiSelector
|
||||||
|
{...mockProps}
|
||||||
|
selectedItems={[]}
|
||||||
|
options={[
|
||||||
|
{ label: 'Project1', value: 'project1' },
|
||||||
|
{ label: 'Project2', value: 'project2' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await user.click(screen.getByLabelText('Projects'));
|
||||||
|
|
||||||
|
const button = screen.queryByRole('button', {
|
||||||
|
name: /select all/i,
|
||||||
|
});
|
||||||
|
expect(button).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can filter options', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<AddonMultiSelector
|
||||||
|
{...mockProps}
|
||||||
|
selectedItems={[]}
|
||||||
|
options={[
|
||||||
|
{ label: 'Alpha', value: 'alpha' },
|
||||||
|
{ label: 'Bravo', value: 'bravo' },
|
||||||
|
{ label: 'Charlie', value: 'charlie' },
|
||||||
|
{ label: 'Alpaca', value: 'alpaca' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const input = await screen.findByLabelText('Projects');
|
||||||
|
await user.type(input, 'alp');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Alpha')).toBeVisible();
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Bravo')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Charlie')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Alpaca')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.clear(input);
|
||||||
|
await user.type(input, 'bravo');
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Bravo')).toBeVisible();
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Alpha')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,184 @@
|
|||||||
|
import React, { ChangeEvent, Fragment, useState, VFC } from 'react';
|
||||||
|
import { IAutocompleteBoxOption } from '../../../common/AutocompleteBox/AutocompleteBox';
|
||||||
|
import { styles as themeStyles } from 'component/common';
|
||||||
|
import {
|
||||||
|
AutocompleteRenderGroupParams,
|
||||||
|
AutocompleteRenderInputParams,
|
||||||
|
AutocompleteRenderOptionState,
|
||||||
|
} from '@mui/material/Autocomplete';
|
||||||
|
import { styled } from '@mui/system';
|
||||||
|
import {
|
||||||
|
Autocomplete,
|
||||||
|
Box,
|
||||||
|
capitalize,
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
Paper,
|
||||||
|
TextField,
|
||||||
|
} from '@mui/material';
|
||||||
|
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
|
||||||
|
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
||||||
|
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { SelectAllButton } from '../../../admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton';
|
||||||
|
|
||||||
|
export interface IAddonMultiSelectorProps {
|
||||||
|
options: IAutocompleteBoxOption[];
|
||||||
|
selectedItems: string[];
|
||||||
|
onChange: (value: string[]) => void;
|
||||||
|
error?: string;
|
||||||
|
onFocus?: () => void;
|
||||||
|
entityName: string;
|
||||||
|
selectAllEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALL_OPTIONS = '*';
|
||||||
|
|
||||||
|
const StyledCheckbox = styled(Checkbox)(() => ({
|
||||||
|
marginRight: '0.2em',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const CustomPaper = ({ ...props }) => <Paper elevation={8} {...props} />;
|
||||||
|
|
||||||
|
export const AddonMultiSelector: VFC<IAddonMultiSelectorProps> = ({
|
||||||
|
options,
|
||||||
|
selectedItems,
|
||||||
|
onChange,
|
||||||
|
error,
|
||||||
|
onFocus,
|
||||||
|
entityName,
|
||||||
|
selectAllEnabled = true,
|
||||||
|
}) => {
|
||||||
|
const [isWildcardSelected, selectWildcard] = useState(
|
||||||
|
selectedItems.includes(ALL_OPTIONS)
|
||||||
|
);
|
||||||
|
const renderInput = (params: AutocompleteRenderInputParams) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
error={Boolean(error)}
|
||||||
|
helperText={error}
|
||||||
|
variant="outlined"
|
||||||
|
label={`${capitalize(entityName)}s`}
|
||||||
|
placeholder={`Select ${entityName}s to filter by`}
|
||||||
|
onFocus={onFocus}
|
||||||
|
data-testid={`select-${entityName}-input`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const isAllSelected =
|
||||||
|
selectedItems.length > 0 &&
|
||||||
|
selectedItems.length === options.length &&
|
||||||
|
selectedItems[0] !== ALL_OPTIONS;
|
||||||
|
|
||||||
|
const onAllItemsChange = (
|
||||||
|
e: ChangeEvent<HTMLInputElement>,
|
||||||
|
checked: boolean
|
||||||
|
) => {
|
||||||
|
if (checked) {
|
||||||
|
selectWildcard(true);
|
||||||
|
onChange([ALL_OPTIONS]);
|
||||||
|
} else {
|
||||||
|
selectWildcard(false);
|
||||||
|
onChange(selectedItems.includes(ALL_OPTIONS) ? [] : selectedItems);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectAllClick = () => {
|
||||||
|
const newItems = isAllSelected ? [] : options.map(({ value }) => value);
|
||||||
|
onChange(newItems);
|
||||||
|
};
|
||||||
|
const renderOption = (
|
||||||
|
props: object,
|
||||||
|
option: IAutocompleteBoxOption,
|
||||||
|
{ selected }: AutocompleteRenderOptionState
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<li {...props}>
|
||||||
|
<StyledCheckbox
|
||||||
|
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
|
||||||
|
checkedIcon={<CheckBoxIcon fontSize="small" />}
|
||||||
|
checked={selected}
|
||||||
|
/>
|
||||||
|
{option.label}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderGroup = ({ key, children }: AutocompleteRenderGroupParams) => (
|
||||||
|
<Fragment key={key}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={options.length > 2 && selectAllEnabled}
|
||||||
|
show={
|
||||||
|
<SelectAllButton
|
||||||
|
isAllSelected={isAllSelected}
|
||||||
|
onClick={onSelectAllClick}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
const SelectAllFormControl = () => (
|
||||||
|
<Box sx={{ mt: 1, mb: 0.25, ml: 1.5 }}>
|
||||||
|
<FormControlLabel
|
||||||
|
data-testid={`select-all-${entityName}s`}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={isWildcardSelected}
|
||||||
|
onChange={onAllItemsChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={`ALL current and future ${entityName}s`}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const HelpText = () => (
|
||||||
|
<p>
|
||||||
|
Selecting {entityName}(s) here will filter events so that your addon
|
||||||
|
will only receive events that are tagged with one of your{' '}
|
||||||
|
{entityName}s.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<h4>{capitalize(entityName)}s</h4>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={selectAllEnabled}
|
||||||
|
show={<HelpText />}
|
||||||
|
/>
|
||||||
|
<span className={themeStyles.error}>{error}</span>
|
||||||
|
<br />
|
||||||
|
<Box sx={{ mt: -1, mb: 3 }}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={selectAllEnabled}
|
||||||
|
show={<SelectAllFormControl />}
|
||||||
|
/>
|
||||||
|
<Autocomplete
|
||||||
|
disabled={isWildcardSelected}
|
||||||
|
multiple
|
||||||
|
limitTags={2}
|
||||||
|
options={options}
|
||||||
|
disableCloseOnSelect
|
||||||
|
getOptionLabel={({ label }) => label}
|
||||||
|
fullWidth
|
||||||
|
groupBy={() => 'Select/Deselect all'}
|
||||||
|
renderGroup={renderGroup}
|
||||||
|
PaperComponent={CustomPaper}
|
||||||
|
renderOption={renderOption}
|
||||||
|
renderInput={renderInput}
|
||||||
|
value={
|
||||||
|
isWildcardSelected
|
||||||
|
? options
|
||||||
|
: options.filter(option =>
|
||||||
|
selectedItems.includes(option.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onChange={(_, input) => {
|
||||||
|
const state = input.map(({ value }) => value);
|
||||||
|
onChange(state);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
@ -10,6 +10,8 @@ export const DEFAULT_DATA = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
parameters: {},
|
parameters: {},
|
||||||
events: [],
|
events: [],
|
||||||
|
projects: [],
|
||||||
|
environments: [],
|
||||||
} as unknown as IAddon; // TODO: improve type
|
} as unknown as IAddon; // TODO: improve type
|
||||||
|
|
||||||
export const CreateAddon = () => {
|
export const CreateAddon = () => {
|
||||||
|
@ -5,6 +5,8 @@ export interface IAddon {
|
|||||||
parameters: Record<string, any>;
|
parameters: Record<string, any>;
|
||||||
id: number;
|
id: number;
|
||||||
events: string[];
|
events: string[];
|
||||||
|
projects?: string[];
|
||||||
|
environments?: string[];
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
@ -34,6 +36,8 @@ export interface IAddonConfig {
|
|||||||
parameters: Record<string, any>;
|
parameters: Record<string, any>;
|
||||||
id: number;
|
id: number;
|
||||||
events: string[];
|
events: string[];
|
||||||
|
projects?: string[];
|
||||||
|
environments?: string[];
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user