mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-03 01:18:43 +02:00
feat: Generate new OAS types, tags component upgrade (#3269)
<!-- Thanks for creating a PR! To make it easier for reviewers and everyone else to understand what your changes relate to, please add some relevant content to the headings below. Feel free to ignore or delete sections that you don't think are relevant. Thank you! ❤️ --> Create/Add/Remove at the same time per tag type ## About the changes <!-- Describe the changes introduced. What are they and why are they being introduced? Feel free to also add screenshots or steps to view the changes if they're visual. --> <!-- Does it close an issue? Multiple? --> Closes # [1-769](https://linear.app/unleash/issue/1-769/refactor-existing-tag-component-to-also-allow-removing-tags) <!-- (For internal contributors): Does it relate to an issue on public roadmap? --> <!-- Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item: # --> ### Important files <!-- PRs can contain a lot of changes, but not all changes are equally important. Where should a reviewer start looking to get an overview of the changes? Are any files particularly important? --> ## Discussion points <!-- Anything about the PR you'd like to discuss before it gets merged? Got any questions or doubts? --> --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai> Co-authored-by: sjaanus <sellinjaanus@gmail.com>
This commit is contained in:
parent
b194bee4ff
commit
efd9e523ed
@ -30,8 +30,5 @@ module.exports = {
|
|||||||
process.env.UNLEASH_OPENAPI_URL ||
|
process.env.UNLEASH_OPENAPI_URL ||
|
||||||
'http://localhost:4242/docs/openapi.json',
|
'http://localhost:4242/docs/openapi.json',
|
||||||
},
|
},
|
||||||
hooks: {
|
|
||||||
afterAllFilesWrite: 'yarn fmt',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
15
frontend/scripts/clean_orval_generated.sh
Executable file
15
frontend/scripts/clean_orval_generated.sh
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Deleting generated apis..."
|
||||||
|
rm -rf src/openapi/apis
|
||||||
|
|
||||||
|
# Remove all but last line from index.ts
|
||||||
|
echo "Cleaning index.ts..."
|
||||||
|
tail -1 src/openapi/index.ts > index_tmp ;
|
||||||
|
cat index_tmp > src/openapi/index.ts ;
|
||||||
|
rm index_tmp
|
||||||
|
|
||||||
|
echo "Formatting..."
|
||||||
|
yarn fmt
|
||||||
|
|
||||||
|
echo "Done!"
|
@ -1,12 +1,12 @@
|
|||||||
import { AutocompleteValue, styled, Typography } from '@mui/material';
|
import { AutocompleteValue, styled, Typography } from '@mui/material';
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||||
import useFeatureTags from 'hooks/api/getters/useFeatureTags/useFeatureTags';
|
import useFeatureTags from 'hooks/api/getters/useFeatureTags/useFeatureTags';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { ITagType } from 'interfaces/tags';
|
import { ITag, ITagType } from 'interfaces/tags';
|
||||||
import { TagOption, TagsInput } from './TagsInput';
|
import { TagOption, TagsInput } from './TagsInput';
|
||||||
import TagTypeSelect from './TagTypeSelect';
|
import TagTypeSelect from './TagTypeSelect';
|
||||||
import useTagApi from 'hooks/api/actions/useTagApi/useTagApi';
|
import useTagApi from 'hooks/api/actions/useTagApi/useTagApi';
|
||||||
@ -26,11 +26,28 @@ const StyledDialogFormContent = styled('section')(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const tagsToOptions = (tags: ITag[]): TagOption[] => {
|
||||||
|
return tags.map(tag => {
|
||||||
|
return {
|
||||||
|
title: tag.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const optionsToTags = (options: TagOption[], type: string): ITag[] => {
|
||||||
|
return options.map(option => {
|
||||||
|
return {
|
||||||
|
value: option.title,
|
||||||
|
type: type,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const { createTag } = useTagApi();
|
const { createTag } = useTagApi();
|
||||||
const { addTagToFeature, loading } = useFeatureApi();
|
const { updateFeatureTags, loading: featureLoading } = useFeatureApi();
|
||||||
const { tags, refetch } = useFeatureTags(featureId);
|
const { tags, refetch, loading: tagsLoading } = useFeatureTags(featureId);
|
||||||
const { setToastData } = useToast();
|
const { setToastData } = useToast();
|
||||||
const [tagType, setTagType] = useState<ITagType>({
|
const [tagType, setTagType] = useState<ITagType>({
|
||||||
name: 'simple',
|
name: 'simple',
|
||||||
@ -38,47 +55,73 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
icon: '',
|
icon: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loading = featureLoading || tagsLoading;
|
||||||
|
|
||||||
|
const [differenceCount, setDifferenceCount] = useState(0);
|
||||||
|
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
|
|
||||||
const [selectedTagOptions, setSelectedTagOptions] = useState<TagOption[]>(
|
const [selectedTagOptions, setSelectedTagOptions] = useState<TagOption[]>(
|
||||||
[]
|
tagsToOptions(tags.filter(tag => tag.type === tagType.name))
|
||||||
);
|
);
|
||||||
|
|
||||||
const { tags: allTags, refetch: refetchAllTags } = useTags(tagType.name);
|
const { tags: allTags, refetch: refetchAllTags } = useTags(tagType.name);
|
||||||
|
|
||||||
const tagTypeOptions: TagOption[] = useMemo(() => {
|
const tagTypeOptions: TagOption[] = useMemo(() => {
|
||||||
return allTags.map(tag => {
|
return tagsToOptions(allTags);
|
||||||
return {
|
|
||||||
title: tag.value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [allTags]);
|
}, [allTags]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tags && tagType) {
|
||||||
|
setSelectedTagOptions(
|
||||||
|
tagsToOptions(tags.filter(tag => tag.type === tagType.name))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [JSON.stringify(tags), tagType]);
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
setSelectedTagOptions([]);
|
setSelectedTagOptions([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (evt: React.SyntheticEvent) => {
|
function difference(array1: ITag[], array2: ITag[]) {
|
||||||
evt.preventDefault();
|
const added = array1
|
||||||
let added = 0;
|
.filter(tag => tag.type === tagType.name)
|
||||||
if (selectedTagOptions.length !== 0) {
|
.filter(
|
||||||
for (const tagOption of selectedTagOptions) {
|
element =>
|
||||||
if (
|
!array2.find(
|
||||||
!tags.includes({
|
e2 =>
|
||||||
type: tagType.name,
|
element.value === e2.value &&
|
||||||
value: tagOption.title,
|
element.type === e2.type
|
||||||
})
|
)
|
||||||
) {
|
);
|
||||||
try {
|
const removed = array2
|
||||||
if (!tagOption.title.startsWith('Create')) {
|
.filter(tag => tag.type === tagType.name)
|
||||||
await addTagToFeature(featureId, {
|
.filter(
|
||||||
type: tagType.name,
|
element =>
|
||||||
value: tagOption.title,
|
!array1.find(
|
||||||
});
|
e2 =>
|
||||||
added++;
|
element.value === e2.value &&
|
||||||
await refetch();
|
element.type === e2.type
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setDifferenceCount(added.length + removed.length);
|
||||||
|
return { added, removed };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const realOptions = (allOptions: TagOption[]) => {
|
||||||
|
return allOptions.filter(
|
||||||
|
tagOption => !tagOption.title.startsWith('Create')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTags = async (added: ITag[], removed: ITag[]) => {
|
||||||
|
try {
|
||||||
|
await updateFeatureTags(featureId, {
|
||||||
|
addedTags: added,
|
||||||
|
removedTags: removed,
|
||||||
|
});
|
||||||
|
await refetch();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const message = formatUnknownError(error);
|
const message = formatUnknownError(error);
|
||||||
setToastData({
|
setToastData({
|
||||||
@ -88,24 +131,53 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
confetti: false,
|
confetti: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getToastText = (addedCount: number, removedCount: number) => {
|
||||||
|
let result = 'We successfully';
|
||||||
|
if (addedCount > 0)
|
||||||
|
result = result.concat(
|
||||||
|
` added ${addedCount} new tag${addedCount > 1 ? 's' : ''}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addedCount > 0 && removedCount > 0) {
|
||||||
|
result = result.concat(' and ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (removedCount > 0) {
|
||||||
|
result = result.concat(
|
||||||
|
` removed ${removedCount} tag${removedCount > 1 ? 's' : ''}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
added > 1 &&
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (evt: React.SyntheticEvent) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
const selectedTags: ITag[] = optionsToTags(
|
||||||
|
realOptions(selectedTagOptions),
|
||||||
|
tagType.name
|
||||||
|
);
|
||||||
|
const { added, removed } = difference(selectedTags, tags);
|
||||||
|
if (differenceCount > 0) {
|
||||||
|
await updateTags(added, removed);
|
||||||
|
differenceCount > 1 &&
|
||||||
trackEvent('suggest_tags', {
|
trackEvent('suggest_tags', {
|
||||||
props: { eventType: 'multiple_tags_added' },
|
props: { eventType: 'multiple_tags_added' },
|
||||||
});
|
});
|
||||||
added > 0 &&
|
differenceCount > 0 &&
|
||||||
setToastData({
|
setToastData({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: `Added tag${added > 1 ? 's' : ''} to toggle`,
|
title: `Updated tag${
|
||||||
text: `We successfully added ${added} new tag${
|
added.length > 1 ? 's' : ''
|
||||||
added > 1 ? 's' : ''
|
} to toggle`,
|
||||||
} to your toggle`,
|
text: getToastText(added.length, removed.length),
|
||||||
confetti: true,
|
confetti: true,
|
||||||
});
|
});
|
||||||
setOpen(false);
|
|
||||||
setSelectedTagOptions([]);
|
|
||||||
}
|
}
|
||||||
|
setDifferenceCount(0);
|
||||||
|
setSelectedTagOptions([]);
|
||||||
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTagTypeChange = (
|
const handleTagTypeChange = (
|
||||||
@ -115,6 +187,8 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
if (value != null && typeof value !== 'string') {
|
if (value != null && typeof value !== 'string') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setTagType(value);
|
setTagType(value);
|
||||||
|
setSelectedTagOptions([]);
|
||||||
|
setDifferenceCount(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -128,8 +202,8 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
>,
|
>,
|
||||||
reason: AutocompleteChangeReason
|
reason: AutocompleteChangeReason
|
||||||
) => {
|
) => {
|
||||||
if (reason === 'selectOption') {
|
|
||||||
const clone = cloneDeep(newValue) as TagOption[];
|
const clone = cloneDeep(newValue) as TagOption[];
|
||||||
|
if (reason === 'selectOption') {
|
||||||
newValue.forEach((value, index) => {
|
newValue.forEach((value, index) => {
|
||||||
if (
|
if (
|
||||||
typeof value !== 'string' &&
|
typeof value !== 'string' &&
|
||||||
@ -151,11 +225,15 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
clone[index] = value;
|
clone[index] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setSelectedTagOptions(clone);
|
|
||||||
}
|
}
|
||||||
};
|
const selectedTags: ITag[] = optionsToTags(
|
||||||
|
realOptions(clone),
|
||||||
|
tagType.name
|
||||||
|
);
|
||||||
|
|
||||||
const hasSelectedValues = selectedTagOptions.length !== 0;
|
difference(selectedTags, tags);
|
||||||
|
setSelectedTagOptions(clone);
|
||||||
|
};
|
||||||
|
|
||||||
const formId = 'add-tag-form';
|
const formId = 'add-tag-form';
|
||||||
|
|
||||||
@ -164,10 +242,10 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
<Dialogue
|
<Dialogue
|
||||||
open={open}
|
open={open}
|
||||||
secondaryButtonText="Cancel"
|
secondaryButtonText="Cancel"
|
||||||
primaryButtonText={`Add tag (${selectedTagOptions.length})`}
|
primaryButtonText={`Save tags`}
|
||||||
title="Add tags to feature toggle"
|
title="Update tags to feature toggle"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
disabledPrimaryButton={loading || !hasSelectedValues}
|
disabledPrimaryButton={loading || differenceCount === 0}
|
||||||
onClose={onCancel}
|
onClose={onCancel}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
>
|
>
|
||||||
@ -187,8 +265,9 @@ const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
|
|||||||
/>
|
/>
|
||||||
<TagsInput
|
<TagsInput
|
||||||
options={tagTypeOptions}
|
options={tagTypeOptions}
|
||||||
tagType={tagType.name}
|
existingTags={tags}
|
||||||
featureTags={tags}
|
tagType={tagType}
|
||||||
|
selectedOptions={selectedTagOptions}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</StyledDialogFormContent>
|
</StyledDialogFormContent>
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
Autocomplete,
|
Autocomplete,
|
||||||
AutocompleteProps,
|
AutocompleteProps,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Chip,
|
||||||
createFilterOptions,
|
createFilterOptions,
|
||||||
FilterOptionsState,
|
FilterOptionsState,
|
||||||
TextField,
|
TextField,
|
||||||
@ -9,9 +10,10 @@ import {
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
|
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
|
||||||
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
||||||
import { ITag } from 'interfaces/tags';
|
import { ITag, ITagType } from 'interfaces/tags';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { Add } from '@mui/icons-material';
|
import { Add } from '@mui/icons-material';
|
||||||
|
import { AutocompleteRenderGetTagProps } from '@mui/material/Autocomplete/Autocomplete';
|
||||||
|
|
||||||
export type TagOption = {
|
export type TagOption = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -19,8 +21,9 @@ export type TagOption = {
|
|||||||
};
|
};
|
||||||
interface ITagsInputProps {
|
interface ITagsInputProps {
|
||||||
options: TagOption[];
|
options: TagOption[];
|
||||||
featureTags: ITag[];
|
existingTags: ITag[];
|
||||||
tagType: string;
|
tagType: ITagType;
|
||||||
|
selectedOptions: TagOption[];
|
||||||
onChange: AutocompleteProps<TagOption | string, true, any, any>['onChange'];
|
onChange: AutocompleteProps<TagOption | string, true, any, any>['onChange'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,19 +31,14 @@ const filter = createFilterOptions<TagOption>();
|
|||||||
|
|
||||||
export const TagsInput = ({
|
export const TagsInput = ({
|
||||||
options,
|
options,
|
||||||
featureTags,
|
selectedOptions,
|
||||||
tagType,
|
tagType,
|
||||||
|
existingTags,
|
||||||
onChange,
|
onChange,
|
||||||
}: ITagsInputProps) => {
|
}: ITagsInputProps) => {
|
||||||
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
|
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
|
||||||
const checkedIcon = <CheckBoxIcon fontSize="small" />;
|
const checkedIcon = <CheckBoxIcon fontSize="small" />;
|
||||||
|
|
||||||
const getOptionDisabled = (option: TagOption) => {
|
|
||||||
return featureTags.some(
|
|
||||||
tag => tag.type === tagType && tag.value === option.title
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOptionLabel = (option: TagOption) => {
|
const getOptionLabel = (option: TagOption) => {
|
||||||
// Add "xxx" option created dynamically
|
// Add "xxx" option created dynamically
|
||||||
if (option.inputValue) {
|
if (option.inputValue) {
|
||||||
@ -57,9 +55,6 @@ export const TagsInput = ({
|
|||||||
option: TagOption,
|
option: TagOption,
|
||||||
{ selected }: { selected: boolean }
|
{ selected }: { selected: boolean }
|
||||||
) => {
|
) => {
|
||||||
const exists = featureTags.some(
|
|
||||||
tag => tag.type === tagType && tag.value === option.title
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<li {...props}>
|
<li {...props}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -70,7 +65,7 @@ export const TagsInput = ({
|
|||||||
icon={icon}
|
icon={icon}
|
||||||
checkedIcon={checkedIcon}
|
checkedIcon={checkedIcon}
|
||||||
sx={{ mr: theme => theme.spacing(0.5) }}
|
sx={{ mr: theme => theme.spacing(0.5) }}
|
||||||
checked={selected || exists}
|
checked={selected}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -79,6 +74,23 @@ export const TagsInput = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderTags = (
|
||||||
|
tagValue: TagOption[],
|
||||||
|
getTagProps: AutocompleteRenderGetTagProps
|
||||||
|
) => {
|
||||||
|
return tagValue.map((option, index) => {
|
||||||
|
const exists = existingTags.some(
|
||||||
|
existingTag =>
|
||||||
|
existingTag.value === option.title &&
|
||||||
|
existingTag.type === tagType.name
|
||||||
|
);
|
||||||
|
if (exists) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <Chip {...getTagProps({ index })} label={option.title} />;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const filterOptions = (
|
const filterOptions = (
|
||||||
options: TagOption[],
|
options: TagOption[],
|
||||||
params: FilterOptionsState<TagOption>
|
params: FilterOptionsState<TagOption>
|
||||||
@ -101,11 +113,13 @@ export const TagsInput = ({
|
|||||||
return (
|
return (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
multiple
|
multiple
|
||||||
id="checkboxes-tags-demo"
|
id="checkboxes-tag"
|
||||||
sx={{ marginTop: theme => theme.spacing(2), width: 500 }}
|
sx={{ marginTop: theme => theme.spacing(2), width: 500 }}
|
||||||
disableCloseOnSelect
|
disableCloseOnSelect
|
||||||
placeholder="Select Values"
|
placeholder="Select Values"
|
||||||
options={options}
|
options={options}
|
||||||
|
value={selectedOptions}
|
||||||
|
renderTags={renderTags}
|
||||||
isOptionEqualToValue={(option, value) => {
|
isOptionEqualToValue={(option, value) => {
|
||||||
if (value.inputValue && value.inputValue !== '') {
|
if (value.inputValue && value.inputValue !== '') {
|
||||||
return option.title === value.inputValue;
|
return option.title === value.inputValue;
|
||||||
@ -113,7 +127,6 @@ export const TagsInput = ({
|
|||||||
return option.title === value.title;
|
return option.title === value.title;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
getOptionDisabled={getOptionDisabled}
|
|
||||||
getOptionLabel={getOptionLabel}
|
getOptionLabel={getOptionLabel}
|
||||||
renderOption={renderOption}
|
renderOption={renderOption}
|
||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions}
|
||||||
|
@ -2,7 +2,7 @@ import { useCallback } from 'react';
|
|||||||
import { ITag } from 'interfaces/tags';
|
import { ITag } from 'interfaces/tags';
|
||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
import { IConstraint } from 'interfaces/strategy';
|
import { IConstraint } from 'interfaces/strategy';
|
||||||
import { CreateFeatureSchema } from 'openapi';
|
import { CreateFeatureSchema, UpdateTagsSchema } from 'openapi';
|
||||||
import useAPI from '../useApi/useApi';
|
import useAPI from '../useApi/useApi';
|
||||||
import { IFeatureVariant } from 'interfaces/featureToggle';
|
import { IFeatureVariant } from 'interfaces/featureToggle';
|
||||||
|
|
||||||
@ -147,6 +147,26 @@ const useFeatureApi = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateFeatureTags = async (
|
||||||
|
featureId: string,
|
||||||
|
update: UpdateTagsSchema
|
||||||
|
) => {
|
||||||
|
// TODO: Change this path to the new API when moved.
|
||||||
|
const path = `api/admin/features/${featureId}/tags`;
|
||||||
|
const req = createRequest(path, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({ ...update }),
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await makeRequest(req.caller, req.id);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const archiveFeatureToggle = async (
|
const archiveFeatureToggle = async (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
featureId: string
|
featureId: string
|
||||||
@ -274,6 +294,7 @@ const useFeatureApi = () => {
|
|||||||
toggleFeatureEnvironmentOff,
|
toggleFeatureEnvironmentOff,
|
||||||
addTagToFeature,
|
addTagToFeature,
|
||||||
deleteTagFromFeature,
|
deleteTagFromFeature,
|
||||||
|
updateFeatureTags,
|
||||||
archiveFeatureToggle,
|
archiveFeatureToggle,
|
||||||
patchFeatureToggle,
|
patchFeatureToggle,
|
||||||
patchFeatureVariants,
|
patchFeatureVariants,
|
||||||
|
Loading…
Reference in New Issue
Block a user