Skip to content

Commit 7fa5828

Browse files
authored
Deprecate SubscriptionField and refactor to use endpoint with server-side filtering (#1957)
* Move SubscriptionField to deprecated * Add basic functionality for surfSubscriptionDropdownOptions * Feature parity * Add missing import * Remove old subscription dropdown options code * Add types/deprecated/ and move FilterParams type into it as SurfSubscriptionDropdownOptionsFilterParams * Create selfish-apricots-hunt.md * Move surf endpoint to deprecated/ and bump changeset to 'minor'
1 parent 401beb0 commit 7fa5828

File tree

16 files changed

+168
-270
lines changed

16 files changed

+168
-270
lines changed

.changeset/selfish-apricots-hunt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@orchestrator-ui/orchestrator-ui-components": minor
3+
---
4+
5+
SURF specific: Deprecate SubscriptionField and refactor it to use new REST endpoint with server-side filtering

packages/orchestrator-ui-components/src/components/WfoForms/formFields/SubscriptionField.tsx renamed to packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/SubscriptionField.tsx

Lines changed: 53 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,12 @@ import {
3333
EuiText,
3434
} from '@elastic/eui';
3535

36-
import { PortMode, ProductTag } from '@/components';
3736
import { useWithOrchestratorTheme } from '@/hooks';
38-
import { useGetSubscriptionDropdownOptions } from '@/hooks/deprecated/useGetSubscriptionDropdownOptions';
39-
import { SubscriptionDropdownOption } from '@/types';
37+
import { useGetSurfSubscriptionDropdownOptions } from '@/hooks/deprecated/useGetSurfSubcriptionDropdownOptions';
4038

41-
import { getSelectFieldStyles } from './SelectField/styles';
39+
import { getSelectFieldStyles } from '../SelectField/styles';
40+
import { FieldProps, Option } from '../types';
4241
import { subscriptionFieldStyling } from './SubscriptionFieldStyling';
43-
import { FieldProps, Option } from './types';
44-
import { getPortMode } from './utils';
4542

4643
declare module 'uniforms' {
4744
interface FilterDOMProps {
@@ -81,6 +78,18 @@ export type SubscriptionFieldProps = FieldProps<
8178
}
8279
>;
8380

81+
function toPortModes(visiblePortMode: string): string[] {
82+
if (visiblePortMode === 'all') {
83+
return [];
84+
}
85+
86+
if (visiblePortMode === 'normal') {
87+
return ['tagged', 'untagged'];
88+
}
89+
90+
return [visiblePortMode];
91+
}
92+
8493
function SubscriptionFieldDefinition({
8594
disabled,
8695
id,
@@ -110,9 +119,6 @@ function SubscriptionFieldDefinition({
110119
const { reactSelectInnerComponentStyles } =
111120
useWithOrchestratorTheme(getSelectFieldStyles);
112121

113-
const { refetch, subscriptions, isFetching } =
114-
useGetSubscriptionDropdownOptions(tags, statuses);
115-
116122
const nameArray = joinName(null, name);
117123
let parentName = joinName(nameArray.slice(0, -1));
118124

@@ -136,160 +142,50 @@ function SubscriptionFieldDefinition({
136142
? get(model, customerKey, 'nonExistingOrgToFilterEverything')
137143
: customerId;
138144

139-
const makeLabel = (subscription: SubscriptionDropdownOption) => {
140-
const description =
141-
subscription.description ||
142-
t('widgets.subscription.missingDescription');
143-
const subscriptionSubstring = subscription.subscriptionId.substring(
144-
0,
145-
8,
146-
);
147-
148-
if (['Node'].includes(subscription.product.tag)) {
149-
const description =
150-
subscription.description ||
151-
t('widgets.subscription.missingDescription');
152-
return `${subscription.subscriptionId.substring(
153-
0,
154-
8,
155-
)} ${description.trim()}`;
156-
} else if (
157-
[
158-
ProductTag.SP,
159-
ProductTag.SPNL,
160-
ProductTag.AGGSP,
161-
ProductTag.AGGSPNL,
162-
ProductTag.MSC,
163-
ProductTag.MSCNL,
164-
ProductTag.IRBSP,
165-
].includes(subscription.product.tag as ProductTag)
145+
const getFilteredOptions = (optionsInput: Option[]): Option[] => {
146+
// Remnant of the old logic in which much more filtering happened clientside, which is now done
147+
// server-side by setting the required URL parameters.
148+
149+
// The 'uniqueItems' filter below should exclude options already chosen in other SubscriptionFields in the same parent Array.
150+
// Although this partly relies on uniforms magic which will be reworked/replaced with pydantic-forms.
151+
if (
152+
parentName !== name &&
153+
parent.fieldType === Array &&
154+
// @ts-expect-error Parent field can have the uniqueItems boolean property but this is not part of JSONSchema6 type
155+
// TODO: Figure out why this is so
156+
parent.uniqueItems
166157
) {
167-
const portMode = getPortMode(subscription.productBlockInstances);
168-
const subscriptionTitle =
169-
subscription.productBlockInstances[0].productBlockInstanceValues.find(
170-
(item) => item.field === 'title',
171-
);
172-
if (subscriptionTitle) {
173-
return `${subscriptionSubstring} - ${description.trim()} - ${
174-
subscriptionTitle.value
175-
}`;
176-
}
177-
return `${subscriptionSubstring} ${portMode?.toUpperCase()} ${description.trim()} ${
178-
subscription.customer?.fullname
179-
}`;
158+
const allValues: string[] = get(model, parentName, []);
159+
const chosenValues = allValues.filter(
160+
(_item, index) =>
161+
index.toString() !== nameArray[nameArray.length - 1],
162+
);
163+
164+
return optionsInput.filter((option) =>
165+
chosenValues.includes(option.value),
166+
);
180167
} else {
181-
return description.trim();
168+
return optionsInput;
182169
}
183170
};
184171

185-
// Filter by product, needed because getSubscriptions might return more than we want
186-
const getSubscriptionOptions = (): Option[] => {
187-
const filteredSubscriptions = subscriptions?.filter((subscription) => {
188-
// NOTE: useBandWith, productIds and tags need to be checked in this order as per the V1 logic
189-
190-
// If a bandwidth filter is supplied it needs to be applied to the subscription product
191-
if (usedBandwidth) {
192-
const portSpeedInput = subscription.fixedInputs.find(
193-
(fixedInput) => fixedInput.field === 'port_speed',
194-
);
195-
if (
196-
portSpeedInput?.value &&
197-
parseInt(portSpeedInput.value.toString(), 10) <
198-
parseInt(usedBandwidth.toString(), 10)
199-
) {
200-
return false;
201-
}
202-
}
203-
204-
// If specific productIds are provided the subscriptions needs to have one of those
205-
if (
206-
!usedBandwidth &&
207-
productIds &&
208-
productIds.length > 0 &&
209-
!productIds.includes(subscription.product.productId)
210-
) {
211-
return false;
212-
}
213-
214-
if (
215-
!usedBandwidth &&
216-
!productIds &&
217-
tags &&
218-
tags?.length > 0 &&
219-
!tags.includes(subscription.product.tag)
220-
) {
221-
return false;
222-
}
223-
224-
// If specific subscriptionIds are excluded the subscription can't be one ot those
225-
if (
226-
excludedSubscriptionIds &&
227-
excludedSubscriptionIds.length > 0 &&
228-
excludedSubscriptionIds.includes(subscription.subscriptionId)
229-
) {
230-
return false;
231-
}
232-
233-
// If a Port mode filter is applied we need to filter on that
234-
if (visiblePortMode !== 'all') {
235-
const portMode = getPortMode(
236-
subscription.productBlockInstances,
237-
);
238-
// For normal mode filter out all subscriptions that don't have tagged or untagged ports
239-
if (
240-
visiblePortMode === 'normal' &&
241-
![PortMode.TAGGED, PortMode.UNTAGGED, undefined].includes(
242-
portMode,
243-
)
244-
) {
245-
return false;
246-
} else if (
247-
portMode !== visiblePortMode &&
248-
visiblePortMode !== 'normal'
249-
) {
250-
return false;
251-
}
252-
}
253-
254-
// If a customer filter is applied we need to filter on that
255-
if (
256-
usedCustomerId &&
257-
subscription.customer?.customerId !== usedCustomerId
258-
) {
259-
return false;
260-
}
261-
262-
if (parentName !== name) {
263-
if (
264-
parent.fieldType === Array &&
265-
// @ts-expect-error Parent field can have the uniqueItems boolean property but this is not part of JSONSchema6 type
266-
// TODO: Figure out why this is so
267-
parent.uniqueItems
268-
) {
269-
const allValues: string[] = get(model, parentName, []);
270-
const chosenValues = allValues.filter(
271-
(_item, index) =>
272-
index.toString() !==
273-
nameArray[nameArray.length - 1],
274-
);
275-
if (!chosenValues.includes(subscription.subscriptionId)) {
276-
return false;
277-
}
278-
}
279-
}
280-
281-
return true;
282-
});
283-
284-
return filteredSubscriptions
285-
? filteredSubscriptions.map((subscription) => ({
286-
label: makeLabel(subscription),
287-
value: subscription.subscriptionId,
288-
}))
289-
: [];
290-
};
172+
const excludeSubscriptionIds = excludedSubscriptionIds;
173+
const portModes = toPortModes(visiblePortMode);
174+
const {
175+
refetch,
176+
options: unfilteredOptions,
177+
isFetching,
178+
} = useGetSurfSubscriptionDropdownOptions(
179+
tags,
180+
statuses,
181+
productIds,
182+
excludeSubscriptionIds,
183+
usedCustomerId,
184+
portModes,
185+
usedBandwidth,
186+
);
291187

292-
const options = getSubscriptionOptions();
188+
const options = getFilteredOptions(unfilteredOptions);
293189

294190
const selectedValue = options.find(
295191
(option: Option) => option.value === value,

packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './VlanField';
1212
export * from './FileUploadField';
1313
export * from './types';
1414
export * from './utils';
15+
export * from './SubscriptionField';

packages/orchestrator-ui-components/src/components/WfoForms/formFields/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export * from './LocationCodeField';
2020
export * from './deprecated';
2121
export * from './NestField';
2222
export * from './OptGroupField';
23-
export * from './SubscriptionField';
2423
export * from './SummaryField';
2524
export * from './CustomerField';
2625
export * from './ConnectedSelectField';

packages/orchestrator-ui-components/src/configuration/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export const IPAM_FREE_SUBNETS_ENDPOINT = `${IPAM_ENDPOINT}/free_subnets`;
2525

2626
//subscriptions
2727
export const SUBSCRIPTION_ACTIONS_ENDPOINT = 'subscriptions/workflows';
28+
export const SUBSCRIPTION_DROPDOWN_OPTIONS_ENDPOINT =
29+
'surf/subscriptions/dropdown-options';
2830
export const CUSTOMER_DESCRIPTION_ENDPOINT =
2931
'/subscription_customer_descriptions';
3032

packages/orchestrator-ui-components/src/hooks/deprecated/useGetSubscriptionDropdownOptions.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useGetSurfSubscriptionDropdownOptionsQuery } from '@/rtk/endpoints/deprecated/surfSubscriptionDropdownOptions';
2+
import { SurfSubscriptionDropdownOptionsFilterParams } from '@/types';
3+
4+
export const useGetSurfSubscriptionDropdownOptions = (
5+
tags: string[] = [],
6+
statuses: string[] = ['active'],
7+
productIds: string[] = [],
8+
excludeSubscriptionIds: string[] = [],
9+
customerId?: string,
10+
portModes: string[] = [],
11+
bandwidth?: number,
12+
) => {
13+
const filter_params: SurfSubscriptionDropdownOptionsFilterParams = {
14+
product_tags: tags,
15+
statuses,
16+
product_ids: productIds,
17+
exclude_subscription_ids: excludeSubscriptionIds,
18+
port_modes: portModes,
19+
bandwidth,
20+
};
21+
22+
if (customerId) {
23+
filter_params.customer_ids = [customerId];
24+
}
25+
26+
const { data, isFetching, refetch, isError } =
27+
useGetSurfSubscriptionDropdownOptionsQuery({ params: filter_params });
28+
29+
const options = (() => {
30+
if (!isFetching && !isError && data) {
31+
return data;
32+
}
33+
return [];
34+
})();
35+
36+
return { isFetching, refetch, options };
37+
};

packages/orchestrator-ui-components/src/rtk/api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export type UseQuery<T, U> = (
6161
type ExtraOptions = {
6262
baseQueryType?: BaseQueryTypes;
6363
apiName?: string;
64+
paramsSerializer?: (params: Record<string, unknown>) => string;
6465
};
6566

6667
export type WfoGraphqlError = {
@@ -131,7 +132,7 @@ export const catchErrorResponse = async (
131132
export const orchestratorApi = createApi({
132133
reducerPath: 'orchestratorApi',
133134
baseQuery: (args, api, extraOptions: ExtraOptions) => {
134-
const { baseQueryType, apiName } = extraOptions || {};
135+
const { baseQueryType, apiName, paramsSerializer } = extraOptions || {};
135136

136137
const state = api.getState() as RootState;
137138
const { orchestratorApiBaseUrl, graphqlEndpointCore, authActive } =
@@ -148,6 +149,7 @@ export const orchestratorApi = createApi({
148149
? customApi.apiBaseUrl
149150
: orchestratorApiBaseUrl,
150151
prepareHeaders,
152+
paramsSerializer,
151153
responseHandler: (response) =>
152154
catchErrorResponse(response, authActive),
153155
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './surfSubscriptionDropdownOptions';

0 commit comments

Comments
 (0)