diff --git a/ui/src/app/flags/rulesApi.ts b/ui/src/app/flags/rulesApi.ts deleted file mode 100644 index 96af8ce3c..000000000 --- a/ui/src/app/flags/rulesApi.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { TagDescription } from '@reduxjs/toolkit/query'; -import { createApi } from '@reduxjs/toolkit/query/react'; - -import { IDistributionBase } from '~/types/Distribution'; -import { IRule, IRuleBase, IRuleList } from '~/types/Rule'; - -import { baseQuery } from '~/utils/redux-rtk'; - -export const ruleTag = (arg: { - namespaceKey: string; - flagKey: string; -}): TagDescription<'Rule'> => { - return { type: 'Rule', id: arg.namespaceKey + '/' + arg.flagKey }; -}; - -export const rulesApi = createApi({ - reducerPath: 'rules', - baseQuery, - tagTypes: ['Rule'], - endpoints: (builder) => ({ - // get list of rules - listRules: builder.query< - IRuleList, - { namespaceKey: string; flagKey: string } - >({ - query: ({ namespaceKey, flagKey }) => - `/namespaces/${namespaceKey}/flags/${flagKey}/rules`, - providesTags: (_result, _error, arg) => [ruleTag(arg)] - }), - // create a new rule - createRule: builder.mutation< - IRule, - { - namespaceKey: string; - flagKey: string; - values: IRuleBase; - distributions: IDistributionBase[]; - } - >({ - queryFn: async ( - { namespaceKey, flagKey, values, distributions }, - _api, - _extraOptions, - baseQuery - ) => { - const respRule = await baseQuery({ - url: `/namespaces/${namespaceKey}/flags/${flagKey}/rules`, - method: 'POST', - body: values - }); - if (respRule.error) { - return { error: respRule.error }; - } - const rule = respRule.data as IRule; - const ruleId = rule.id; - // then create the distributions - for (let distribution of distributions) { - const resp = await baseQuery({ - url: `/namespaces/${namespaceKey}/flags/${flagKey}/rules/${ruleId}/distributions`, - method: 'POST', - body: distribution - }); - if (resp.error) { - return { error: resp.error }; - } - } - return { data: rule }; - }, - invalidatesTags: (_result, _error, arg) => [ruleTag(arg)] - }), - // delete the rule - deleteRule: builder.mutation< - void, - { namespaceKey: string; flagKey: string; ruleId: string } - >({ - query({ namespaceKey, flagKey, ruleId }) { - return { - url: `/namespaces/${namespaceKey}/flags/${flagKey}/rules/${ruleId}`, - method: 'DELETE' - }; - }, - invalidatesTags: (_result, _error, arg) => [ruleTag(arg)] - }), - // update the rule - updateRule: builder.mutation< - IRule, - { - namespaceKey: string; - flagKey: string; - ruleId: string; - values: IRuleBase; - } - >({ - query({ namespaceKey, flagKey, ruleId, values }) { - return { - url: `/namespaces/${namespaceKey}/flags/${flagKey}/rules/${ruleId}`, - method: 'PUT', - body: values - }; - }, - invalidatesTags: (_result, _error, arg) => [ruleTag(arg)] - }), - // reorder the rules - orderRules: builder.mutation< - IRule, - { - namespaceKey: string; - flagKey: string; - ruleIds: string[]; - } - >({ - query({ namespaceKey, flagKey, ruleIds }) { - return { - url: `/namespaces/${namespaceKey}/flags/${flagKey}/rules/order`, - method: 'PUT', - body: { ruleIds: ruleIds } - }; - }, - async onQueryStarted( - { namespaceKey, flagKey, ruleIds }, - { dispatch, queryFulfilled } - ) { - // this is manual optimistic cache update of the listRules - // to set a desire order of rules of the listRules while server is updating the state. - // If we don't do this we will have very strange UI state with rules in old order - // until the result will be get from the server. It's very visible on slow connections. - const patchResult = dispatch( - rulesApi.util.updateQueryData( - 'listRules', - { namespaceKey, flagKey }, - (draft: IRuleList) => { - const rules = draft.rules; - const resortedRules = rules.sort((a, b) => { - const ida = ruleIds.indexOf(a.id); - const idb = ruleIds.indexOf(b.id); - if (ida < idb) { - return -1; - } else if (ida > idb) { - return 1; - } - return 0; - }); - return Object.assign(draft, { rules: resortedRules }); - } - ) - ); - try { - await queryFulfilled; - } catch { - patchResult.undo(); - } - }, - invalidatesTags: (_result, _error, arg) => [ruleTag(arg)] - }), - // update the dustribution - updateDistribution: builder.mutation< - IRule, - { - namespaceKey: string; - flagKey: string; - ruleId: string; - distributionId: string; - values: IDistributionBase; - } - >({ - query({ namespaceKey, flagKey, ruleId, distributionId, values }) { - return { - url: `/namespaces/${namespaceKey}/flags/${flagKey}/rules/${ruleId}/distributions/${distributionId}`, - method: 'PUT', - body: values - }; - }, - invalidatesTags: (_result, _error, arg) => [ruleTag(arg)] - }) - }) -}); -export const { - useListRulesQuery, - useCreateRuleMutation, - useDeleteRuleMutation, - useUpdateRuleMutation, - useOrderRulesMutation, - useUpdateDistributionMutation -} = rulesApi; diff --git a/ui/src/app/segments/segmentsApi.ts b/ui/src/app/segments/segmentsApi.ts index dfee94e45..6c6bb38f9 100644 --- a/ui/src/app/segments/segmentsApi.ts +++ b/ui/src/app/segments/segmentsApi.ts @@ -1,6 +1,7 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { createApi } from '@reduxjs/toolkit/query/react'; import { SortingState } from '@tanstack/react-table'; +import { v4 as uuid } from 'uuid'; import { IConstraint } from '~/types/Constraint'; import { IResourceListResponse, IResourceResponse } from '~/types/Resource'; @@ -86,9 +87,9 @@ export const segmentsApi = createApi({ return { ...response.resource.payload, constraints: response.resource.payload.constraints?.map( - (c: IConstraint, i: number) => ({ + (c: IConstraint) => ({ ...c, - rank: i + id: uuid() }) ) }; diff --git a/ui/src/components/flags/FlagFormContext.tsx b/ui/src/components/flags/FlagFormContext.tsx index d42691070..51015beae 100644 --- a/ui/src/components/flags/FlagFormContext.tsx +++ b/ui/src/components/flags/FlagFormContext.tsx @@ -2,7 +2,7 @@ import { FormikProps } from 'formik'; import React, { ReactNode, createContext } from 'react'; import { v4 as uuid } from 'uuid'; -import { IFlag, IFlagBase } from '~/types/Flag'; +import { IFlag } from '~/types/Flag'; import { IRollout } from '~/types/Rollout'; import { IRule } from '~/types/Rule'; import { IVariant } from '~/types/Variant'; @@ -11,7 +11,7 @@ interface FlagFormContextProps { variants: IVariant[]; rules: IRule[]; rollouts: IRollout[]; - updateFlag: (f: Partial) => void; + updateFlag: (f: Partial) => void; setRules: (rules: IRule[]) => void; createRule: (r: IRule) => void; updateRule: (r: IRule) => void; @@ -57,7 +57,7 @@ export const FlagFormProvider: React.FC = ({ }) => { const { rules, variants, rollouts } = formik.values; - const updateFlag = (f: Partial) => { + const updateFlag = (f: Partial) => { if ('name' in f) { formik.setFieldValue('name', f.name); } @@ -104,14 +104,13 @@ export const FlagFormProvider: React.FC = ({ }; const createVariant = (v: IVariant) => { - v.id = uuid(); const newVariants = [...(variants || []), v]; formik.setFieldValue('variants', newVariants); }; const updateVariant = (v: IVariant) => { const newVariants = [...(variants || [])]; - const index = newVariants.findIndex((variant) => variant.id === v.id); + const index = newVariants.findIndex((variant) => variant.key === v.key); newVariants[index] = v; formik.setFieldValue('variants', newVariants); }; @@ -125,7 +124,7 @@ export const FlagFormProvider: React.FC = ({ }); const newVariants = [...(variants || [])]; - const index = newVariants.findIndex((variant) => variant.id === v.id); + const index = newVariants.findIndex((variant) => variant.key === v.key); newVariants.splice(index, 1); formik.setFieldValue('variants', newVariants); }; diff --git a/ui/src/components/rules/SortableRule.tsx b/ui/src/components/rules/SortableRule.tsx index 0c48718e0..9bc9301b4 100644 --- a/ui/src/components/rules/SortableRule.tsx +++ b/ui/src/components/rules/SortableRule.tsx @@ -26,7 +26,7 @@ export default function SortableRule(props: SortableRuleProps) { transform, transition } = useSortable({ - id: rule.id + id: rule.id! }); const style = transform diff --git a/ui/src/store.ts b/ui/src/store.ts index 9ced0c757..7695a2b51 100644 --- a/ui/src/store.ts +++ b/ui/src/store.ts @@ -20,7 +20,6 @@ import { eventKey, eventSlice } from './app/events/eventSlice'; import { analyticsApi } from './app/flags/analyticsApi'; import { flagsApi, flagsTableSlice } from './app/flags/flagsApi'; import { rolloutsApi } from './app/flags/rolloutsApi'; -import { rulesApi } from './app/flags/rulesApi'; import { metaSlice } from './app/meta/metaSlice'; import { preferencesKey, @@ -129,7 +128,6 @@ export const store = configureStore({ [namespaceApi.reducerPath]: namespaceApi.reducer, [flagsApi.reducerPath]: flagsApi.reducer, [segmentsApi.reducerPath]: segmentsApi.reducer, - [rulesApi.reducerPath]: rulesApi.reducer, [rolloutsApi.reducerPath]: rolloutsApi.reducer, [tokensApi.reducerPath]: tokensApi.reducer, [authProvidersApi.reducerPath]: authProvidersApi.reducer, @@ -143,7 +141,6 @@ export const store = configureStore({ namespaceApi.middleware, flagsApi.middleware, segmentsApi.middleware, - rulesApi.middleware, rolloutsApi.middleware, tokensApi.middleware, authProvidersApi.middleware, diff --git a/ui/src/types/Constraint.ts b/ui/src/types/Constraint.ts index d2f775492..00d557913 100644 --- a/ui/src/types/Constraint.ts +++ b/ui/src/types/Constraint.ts @@ -1,4 +1,5 @@ export interface IConstraint { + id?: string; property: string; type: ConstraintType; operator: string;