Skip to content

Commit 2538b09

Browse files
Merge pull request #9767 from ImalshaD/ImalshaD/conditional-rendering-on-feature-preview-modal
2 parents 86fa867 + 90ce118 commit 2538b09

File tree

4 files changed

+259
-217
lines changed

4 files changed

+259
-217
lines changed

.changeset/six-books-clap.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@wso2is/admin.core.v1": patch
3+
"@wso2is/console": patch
4+
---
5+
6+
Add Conditional Feature Preview Rendering Logic.

features/admin.core.v1/components/header.tsx

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (c) 2023-2025, WSO2 LLC. (https://www.wso2.com).
2+
* Copyright (c) 2023-2026, WSO2 LLC. (https://www.wso2.com).
33
*
44
* WSO2 LLC. licenses this file to you under the Apache License,
55
* Version 2.0 (the "License"); you may not use this file except
@@ -71,6 +71,7 @@ import { AppConstants } from "../constants/app-constants";
7171
import { OrganizationType } from "../constants/organization-constants";
7272
import { history } from "../helpers/history";
7373
import useGlobalVariables from "../hooks/use-global-variables";
74+
import { usePreviewFeatures } from "../hooks/use-preview-features";
7475
import { ConfigReducerStateInterface } from "../models/reducer-state";
7576
import { AppState, store } from "../store";
7677
import { CommonUtils } from "../utils/common-utils";
@@ -96,6 +97,7 @@ const Header: FunctionComponent<HeaderPropsInterface> = ({
9697
const { t } = useTranslation();
9798

9899
const { showPreviewFeaturesModal, setShowPreviewFeaturesModal } = useFeatureGate();
100+
const { canUsePreviewFeatures } = usePreviewFeatures();
99101
const { config: runtimeConfig } = useRuntimeConfig();
100102

101103
const profileInfo: ProfileInfoInterface = useSelector((state: AppState) => state.profile.profileInfo);
@@ -123,12 +125,6 @@ const Header: FunctionComponent<HeaderPropsInterface> = ({
123125
const supportedI18nLanguages: SupportedLanguagesMeta = useSelector(
124126
(state: AppState) => state.global.supportedI18nLanguages
125127
);
126-
const loginAndRegistrationFeatureConfig: FeatureAccessConfigInterface =
127-
useSelector((state: AppState) => state?.config?.ui?.features?.loginAndRegistration);
128-
129-
const cdsFeatureConfig: FeatureAccessConfigInterface =
130-
useSelector((state: AppState) => state?.config?.ui?.features?.customerDataService);
131-
132128
const isCentralDeploymentEnabled: boolean = useSelector((state: AppState) => {
133129
return state?.config?.deployment?.centralDeploymentEnabled;
134130
});
@@ -682,22 +678,14 @@ const Header: FunctionComponent<HeaderPropsInterface> = ({
682678
</MenuItem>
683679
</Show>
684680
),
685-
<Show key="feature.preview" featureId={ FeatureGateConstants.SAAS_FEATURES_IDENTIFIER }>
686-
<Show
687-
when={ [
688-
...(loginAndRegistrationFeatureConfig?.scopes?.update ?? []),
689-
...(cdsFeatureConfig?.scopes?.update ?? [])
690-
] }
691-
featureId={ FeatureGateConstants.PREVIEW_FEATURES_IDENTIFIER }
692-
>
693-
<MenuItem onClick={ () => setShowPreviewFeaturesModal(true) }>
694-
<ListItemIcon>
695-
<PreviewFeaturesIcon />
696-
</ListItemIcon>
697-
<ListItemText>{ t("Feature Preview") }</ListItemText>
698-
</MenuItem>
699-
</Show>
700-
</Show>,
681+
canUsePreviewFeatures && (
682+
<MenuItem onClick={ () => setShowPreviewFeaturesModal(true) }>
683+
<ListItemIcon>
684+
<PreviewFeaturesIcon />
685+
</ListItemIcon>
686+
<ListItemText>{ t("Feature Preview") }</ListItemText>
687+
</MenuItem>
688+
),
701689
isShowAppSwitchButton() ? (
702690
<MenuItem
703691
color="inherit"
@@ -726,10 +714,12 @@ const Header: FunctionComponent<HeaderPropsInterface> = ({
726714
} }
727715
{ ...rest }
728716
/>
729-
<FeaturePreviewModal
730-
open={ showPreviewFeaturesModal }
731-
onClose={ () => setShowPreviewFeaturesModal(false) }
732-
/>
717+
{ canUsePreviewFeatures && (
718+
<FeaturePreviewModal
719+
open={ showPreviewFeaturesModal }
720+
onClose={ () => setShowPreviewFeaturesModal(false) }
721+
/>
722+
) }
733723
</>
734724
);
735725
};

features/admin.core.v1/components/modals/feature-preview-modal.tsx

Lines changed: 12 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
2+
* Copyright (c) 2025-2026, WSO2 LLC. (https://www.wso2.com).
33
*
44
* WSO2 LLC. licenses this file to you under the Apache License,
55
* Version 2.0 (the "License"); you may not use this file except
@@ -34,84 +34,16 @@ import ListItemText from "@oxygen-ui/react/ListItemText";
3434
import Stack from "@oxygen-ui/react/Stack";
3535
import Switch from "@oxygen-ui/react/Switch";
3636
import Typography from "@oxygen-ui/react/Typography";
37-
import { useRequiredScopes } from "@wso2is/access-control";
38-
import { updateCDSConfig } from "@wso2is/admin.cds.v1/api/config";
39-
import useCDSConfig from "@wso2is/admin.cds.v1/hooks/use-config";
40-
import useFeatureGate from "@wso2is/admin.feature-gate.v1/hooks/use-feature-gate";
41-
import { AlertLevels, FeatureAccessConfigInterface, IdentifiableComponentInterface } from "@wso2is/core/models";
42-
import { addAlert } from "@wso2is/core/store";
43-
import React, { ChangeEvent, FunctionComponent, ReactElement, useEffect, useMemo, useState } from "react";
37+
import { IdentifiableComponentInterface } from "@wso2is/core/models";
38+
import React, { ChangeEvent, FunctionComponent, ReactElement } from "react";
4439
import { useTranslation } from "react-i18next";
45-
import { useDispatch, useSelector } from "react-redux";
46-
import NewCDSFeatureImage from "../../assets/illustrations/preview-features/new-cds-feature.png";
47-
import { AppConstants } from "../../constants/app-constants";
48-
import { history } from "../../helpers/history";
49-
import { AppState } from "../../store";
50-
import "./feature-preview-modal.scss";
51-
52-
/** Added or removed as a system application when CDS is toggled. */
53-
const CDS_CONSOLE_APP:string = "CONSOLE";
40+
import { PreviewFeaturesListInterface, usePreviewFeatures } from "../../hooks/use-preview-features";
5441

5542
interface FeaturePreviewModalPropsInterface extends IdentifiableComponentInterface {
5643
open: boolean;
5744
onClose: () => void;
5845
}
5946

60-
/**
61-
* Preview features list interface.
62-
*/
63-
interface PreviewFeaturesListInterface {
64-
/**
65-
* Feature action.
66-
*/
67-
action?: string;
68-
69-
/**
70-
* Feature name.
71-
*/
72-
name: string;
73-
74-
/**
75-
* React component to be rendered
76-
*/
77-
component?: ReactElement;
78-
79-
/**
80-
* Feature description.
81-
*/
82-
description: string;
83-
84-
/**
85-
* Feature id.
86-
*/
87-
id: string;
88-
89-
/**
90-
* Feature image.
91-
*/
92-
image?: string;
93-
94-
/**
95-
* Whether the feature is enabled
96-
*/
97-
enabled?: boolean;
98-
99-
/**
100-
* Feature value.
101-
*/
102-
value: string;
103-
104-
/**
105-
* Required scopes to access the feature. If not provided, the feature will be accessible to all users.
106-
*/
107-
requiredScopes?: string[];
108-
109-
message?: {
110-
type: "info" | "warning" | "error";
111-
content: string;
112-
};
113-
}
114-
11547
/**
11648
* Feature preview modal component.
11749
*
@@ -123,129 +55,19 @@ const FeaturePreviewModal: FunctionComponent<FeaturePreviewModalPropsInterface>
12355
onClose,
12456
open
12557
}: FeaturePreviewModalPropsInterface): ReactElement => {
126-
12758
const { t } = useTranslation();
128-
const dispatch: any = useDispatch();
129-
const { selectedPreviewFeatureToShow } = useFeatureGate();
130-
131-
const cdsFeatureConfig: FeatureAccessConfigInterface = useSelector(
132-
(state: AppState) => state?.config?.ui?.features?.customerDataService
133-
);
134-
13559
const {
136-
data: cdsConfig,
137-
mutate: mutateCDSConfig
138-
} = useCDSConfig(open && (cdsFeatureConfig?.enabled ?? false));
139-
140-
const hasCDSScopes: boolean = useRequiredScopes(
141-
cdsFeatureConfig?.scopes?.update
142-
);
143-
144-
145-
const previewFeaturesList: PreviewFeaturesListInterface[] = useMemo(() => ([
146-
{
147-
action: t("customerDataService:common.featurePreview.action"),
148-
description: t("customerDataService:common.featurePreview.description"),
149-
enabled: cdsConfig?.cds_enabled,
150-
id: "customer-data-service",
151-
image: NewCDSFeatureImage,
152-
message: {
153-
content: t("customerDataService:common.featurePreview.message"),
154-
type: "warning" as const
155-
},
156-
name: t("customerDataService:common.featurePreview.name"),
157-
requiredScopes: cdsFeatureConfig?.scopes?.update,
158-
value: "CDS.Enable"
159-
}
160-
].filter(Boolean)), [ cdsConfig, cdsFeatureConfig, t ]);
161-
162-
const accessibleFeatures: PreviewFeaturesListInterface[] = useMemo(() => (
163-
previewFeaturesList.filter((feature: PreviewFeaturesListInterface) => {
164-
if (feature.id === "customer-data-service") {
165-
return hasCDSScopes && !!cdsFeatureConfig?.enabled;
166-
}
167-
168-
return true;
169-
})
170-
), [ previewFeaturesList, hasCDSScopes, cdsFeatureConfig?.enabled ]);
171-
172-
const [ selectedFeatureIndex, setSelectedFeatureIndex ] = useState(0);
173-
174-
const selected: PreviewFeaturesListInterface = useMemo(
175-
() => accessibleFeatures[selectedFeatureIndex],
176-
[ selectedFeatureIndex, accessibleFeatures ]
177-
);
178-
179-
useEffect(() => {
180-
const activePreviewFeatureIndex: number = accessibleFeatures.findIndex(
181-
(feature: PreviewFeaturesListInterface) => feature?.id === selectedPreviewFeatureToShow
182-
);
183-
184-
setSelectedFeatureIndex(activePreviewFeatureIndex > 0 ? activePreviewFeatureIndex : 0);
185-
}, [ selectedPreviewFeatureToShow ]);
186-
187-
const handleClose = () => {
60+
accessibleFeatures,
61+
handlePageRedirection,
62+
handleToggleChange,
63+
selected,
64+
setSelectedFeatureIndex
65+
} = usePreviewFeatures();
66+
67+
const handleClose: () => void = (): void => {
18868
onClose();
18969
};
19070

191-
const handlePageRedirection = (actionId: string) => {
192-
switch (actionId) {
193-
case "customer-data-service":
194-
return history.push(AppConstants.getPaths().get("PROFILES"));
195-
default:
196-
return;
197-
}
198-
};
199-
200-
const handleToggleChange = async (e: ChangeEvent<HTMLInputElement>, actionId: string) => {
201-
const isChecked: boolean = e.target.checked;
202-
203-
switch (actionId) {
204-
case "customer-data-service":
205-
await handleCDSToggle(isChecked);
206-
207-
break;
208-
209-
default:
210-
break;
211-
}
212-
};
213-
214-
/**
215-
* Handles CDS enable/disable via PATCH.
216-
*
217-
* Enabling → set cds_enabled: true; if system_applications is empty, seed it with ["CONSOLE"].
218-
* Disabling → set cds_enabled: false; remove "CONSOLE" from system_applications (leave others intact).
219-
*/
220-
const handleCDSToggle = async (enable: boolean): Promise<void> => {
221-
const currentApps: string[] = cdsConfig?.system_applications ?? [];
222-
223-
let nextApps: string[];
224-
225-
if (enable) {
226-
nextApps = currentApps.length === 0
227-
? [ CDS_CONSOLE_APP ]
228-
: currentApps;
229-
} else {
230-
nextApps = currentApps.filter((app: string) => app !== CDS_CONSOLE_APP);
231-
}
232-
233-
try {
234-
await updateCDSConfig({
235-
cds_enabled: enable,
236-
system_applications: nextApps
237-
});
238-
239-
mutateCDSConfig();
240-
} catch (error) {
241-
dispatch(addAlert({
242-
description: t("customerDataService:common.featurePreview.updateError"),
243-
level: AlertLevels.ERROR,
244-
message: t("common:error")
245-
}));
246-
}
247-
};
248-
24971
return (
25072
<Dialog
25173
onClose={ handleClose }

0 commit comments

Comments
 (0)