From a911e5ac11cea6b4fa74bb28b74443a254302748 Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Wed, 4 Sep 2024 16:30:02 +0530 Subject: [PATCH 1/7] Fix state delay in shared application tab in application edit page --- .../forms/share-application-form.tsx | 234 +++++++++++------- .../configs/application.tsx | 201 +-------------- .../configs/models/application.ts | 1 - .../api/use-shared-organizations.ts | 69 ++++++ 4 files changed, 219 insertions(+), 286 deletions(-) create mode 100644 features/admin.organizations.v1/api/use-shared-organizations.ts diff --git a/features/admin.applications.v1/components/forms/share-application-form.tsx b/features/admin.applications.v1/components/forms/share-application-form.tsx index 4d97cd097ef..4e7e75aa824 100644 --- a/features/admin.applications.v1/components/forms/share-application-form.tsx +++ b/features/admin.applications.v1/components/forms/share-application-form.tsx @@ -20,26 +20,24 @@ import Collapse from "@mui/material/Collapse"; import { AppState } from "@wso2is/admin.core.v1"; import useGlobalVariables from "@wso2is/admin.core.v1/hooks/use-global-variables"; import { - getOrganizations, - getSharedOrganizations, shareApplication, stopSharingApplication, unshareApplication } from "@wso2is/admin.organizations.v1/api"; +import useGetOrganizations from "@wso2is/admin.organizations.v1/api/use-get-organizations"; +import useSharedOrganizations from "@wso2is/admin.organizations.v1/api/use-shared-organizations"; import { OrganizationInterface, - OrganizationListInterface, OrganizationResponseInterface, ShareApplicationRequestInterface } from "@wso2is/admin.organizations.v1/models"; -import { IdentityAppsError } from "@wso2is/core/errors"; -import { IdentityAppsApiException } from "@wso2is/core/exceptions"; import { AlertLevels, IdentifiableComponentInterface } from "@wso2is/core/models"; import { addAlert } from "@wso2is/core/store"; import { + ContentLoader, Heading, Hint, Message, @@ -48,7 +46,7 @@ import { TransferList, TransferListItem } from "@wso2is/react-components"; -import { AxiosError, AxiosResponse } from "axios"; +import { AxiosError } from "axios"; import differenceBy from "lodash-es/differenceBy"; import escapeRegExp from "lodash-es/escapeRegExp"; import isEmpty from "lodash-es/isEmpty"; @@ -57,6 +55,7 @@ import React, { FunctionComponent, useCallback, useEffect, + useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -123,6 +122,137 @@ export const ApplicationShareForm: FunctionComponent(); const { isOrganizationManagementEnabled } = useGlobalVariables(); + const { + data: organizations, + isLoading: isOrganizationsFetchRequestLoading, + isValidating: isOrganizationsFetchRequestValidating, + error: organizationsFetchRequestError + } = useGetOrganizations( + isOrganizationManagementEnabled, + null, + null, + null, + null, + true, + false + ); + + const { + data: sharedOrganizations, + isLoading: isSharedOrganizationsFetchRequestLoading, + isValidating: isSharedOrganizationsFetchRequestValidating, + error: sharedOrganizationsFetchRequestError, + mutate: mutateSharedOrganizationsFetchRequest + } = useSharedOrganizations( + application.id, + isOrganizationManagementEnabled + ); + + /** + * Check if the organization list is loading. + */ + const isLoading: boolean = useMemo(() => { + if (!isOrganizationManagementEnabled) { + return false; + } + + return isOrganizationsFetchRequestLoading || + isSharedOrganizationsFetchRequestLoading || + isOrganizationsFetchRequestValidating || + isSharedOrganizationsFetchRequestValidating; + }, [ + isOrganizationsFetchRequestLoading, + isSharedOrganizationsFetchRequestLoading, + isOrganizationsFetchRequestValidating, + isSharedOrganizationsFetchRequestValidating + ]); + + /** + * Fetches the organization list. + */ + useEffect(() => { + if (organizations?.organizations) { + setSubOrganizationList(organizations.organizations); + } + }, [ organizations ]); + + /** + * Fetches the shared organizations list for the particular application. + */ + useEffect(() => { + if (sharedOrganizations?.organizations) { + setSharedOrganizationList(sharedOrganizations.organizations); + } else { + setSharedOrganizationList([]); + } + }, [ sharedOrganizations ]); + + /** + * Dispatches error notifications if organization fetch request fails. + */ + useEffect(() => { + if (!organizationsFetchRequestError) { + return; + } + + if (organizationsFetchRequestError?.response?.data?.description) { + dispatch(addAlert({ + description: organizationsFetchRequestError?.response?.data?.description + ?? t("organizations:notifications.getOrganizationList.error.description"), + level: AlertLevels.ERROR, + message: organizationsFetchRequestError?.response?.data?.message + ?? t("organizations:notifications.getOrganizationList.error.message") + })); + + return; + } + + dispatch( + addAlert({ + description: t( + "organizations:notifications.getOrganizationList" + + ".genericError.description" + ), + level: AlertLevels.ERROR, + message: t( + "organizations:notifications." + + "getOrganizationList.genericError.message" + ) + }) + ); + }, [ organizationsFetchRequestError ]); + + /** + * Dispatches error notifications if shared organizations fetch request fails. + */ + useEffect(() => { + if (!sharedOrganizationsFetchRequestError) { + return; + } + + if (sharedOrganizationsFetchRequestError?.response?.data?.description) { + dispatch(addAlert({ + description: sharedOrganizationsFetchRequestError?.response?.data?.description + ?? t("applications:edit.sections.shareApplication.getSharedOrganizations.genericError.description"), + level: AlertLevels.ERROR, + message: sharedOrganizationsFetchRequestError?.response?.data?.message + ?? t("applications:edit.sections.shareApplication.getSharedOrganizations.genericError.message") + })); + + return; + } + + dispatch( + addAlert({ + description: t("applications:edit.sections.shareApplication" + + ".getSharedOrganizations.genericError.description"), + level: AlertLevels.ERROR, + message: t("applications:edit.sections.shareApplication" + + ".getSharedOrganizations.genericError.message") + }) + ); + }, [ sharedOrganizationsFetchRequestError ]); + useEffect(() => setTempOrganizationList(subOrganizationList || []), [ subOrganizationList ] ); @@ -171,90 +301,6 @@ export const ApplicationShareForm: FunctionComponent { - if (!isOrganizationManagementEnabled) { - return; - } - - getOrganizations( - null, - null, - null, - null, - true, - false - ).then((response: OrganizationListInterface) => { - setSubOrganizationList(response.organizations); - }).catch((error: IdentityAppsError) => { - if (error?.description) { - dispatch( - addAlert({ - description: error.description, - level: AlertLevels.ERROR, - message: t( - "organizations:notifications." + - "getOrganizationList.error.message" - ) - }) - ); - - return; - } - - dispatch( - addAlert({ - description: t( - "organizations:notifications.getOrganizationList" + - ".genericError.description" - ), - level: AlertLevels.ERROR, - message: t( - "organizations:notifications." + - "getOrganizationList.genericError.message" - ) - }) - ); - }); - - fetchSharedOrganizationsList(); - }, [ getOrganizations ]); - - const fetchSharedOrganizationsList = (): void => { - getSharedOrganizations( - currentOrganization.id, - application.id - ).then((response: AxiosResponse) => { - setSharedOrganizationList(response.data.organizations); - }).catch((error: IdentityAppsApiException) => { - if (error.response.data.description) { - dispatch( - addAlert({ - description: error.response.data.description, - level: AlertLevels.ERROR, - message: t("applications:edit.sections.shareApplication" + - ".getSharedOrganizations.genericError.message") - }) - ); - - return; - } - - dispatch( - addAlert({ - description: t("applications:edit.sections.shareApplication" + - ".getSharedOrganizations.genericError.description"), - level: AlertLevels.ERROR, - message: t("applications:edit.sections.shareApplication" + - ".getSharedOrganizations.genericError.message") - }) - ); - }); - }; - const handleShareApplication: () => Promise = useCallback(async () => { let shareAppData: ShareApplicationRequestInterface; let removedOrganization: OrganizationInterface[]; @@ -342,7 +388,7 @@ export const ApplicationShareForm: FunctionComponent { if (shareType === ShareType.SHARE_SELECTED) { - fetchSharedOrganizationsList(); + mutateSharedOrganizationsFetchRequest(); } onApplicationSharingCompleted(); @@ -514,6 +560,12 @@ export const ApplicationShareForm: FunctionComponent + ); + }; + return ( <> diff --git a/features/admin.extensions.v1/configs/application.tsx b/features/admin.extensions.v1/configs/application.tsx index 416e2ab3dcc..cfda98195ed 100644 --- a/features/admin.extensions.v1/configs/application.tsx +++ b/features/admin.extensions.v1/configs/application.tsx @@ -31,32 +31,22 @@ import { } from "@wso2is/admin.applications.v1/models"; import getTryItClientId from "@wso2is/admin.applications.v1/utils/get-try-it-client-id"; import { ClaimManagementConstants } from "@wso2is/admin.claims.v1/constants/claim-management-constants"; -import { EventPublisher, FeatureConfigInterface } from "@wso2is/admin.core.v1"; +import { FeatureConfigInterface } from "@wso2is/admin.core.v1"; import { AppConstants } from "@wso2is/admin.core.v1/constants"; import { ApplicationRoles } from "@wso2is/admin.roles.v2/components/application-roles"; import { I18n } from "@wso2is/i18n"; import { Code, - DocumentationLink, - EmphasizedSegment, - GenericIcon, Heading, - Popup, - PrimaryButton, ResourceTab, - ResourceTabPaneInterface, - Text + ResourceTabPaneInterface } from "@wso2is/react-components"; import React, { ReactElement } from "react"; import { Trans } from "react-i18next"; -import { Divider, Icon, Message } from "semantic-ui-react"; -import { ApplicationGeneralTabOverride } from "./components/application-general-tab-overide"; +import { Divider, Icon } from "semantic-ui-react"; import { MarketingConsentModalWrapper } from "./components/marketing-consent/components"; import { ApplicationConfig, ExtendedFeatureConfigInterface } from "./models"; import { ApplicationTabIDs } from "./models/application"; -import { - ReactComponent as TryItAppIllustration -} from "../../themes/default/assets/images/illustrations/rafiki-illustration.svg"; import MobileAppTemplate from "../application-templates/templates/mobile-application/mobile-application.json"; import OIDCWebAppTemplate from "../application-templates/templates/oidc-web-application/oidc-web-application.json"; import SamlWebAppTemplate @@ -210,187 +200,10 @@ export const applicationConfig: ApplicationConfig = { }, editApplication: { extendTabs: false, - getActions: (applicationId: string, clientId: string, tenant: string, testId: string) => { - - const asgardeoLoginPlaygroundURL: string = window[ "AppUtils" ]?.getConfig()?.extensions?.asgardeoTryItURL; - - if (clientId === getTryItClientId(tenant)) { - return ( - { - EventPublisher.getInstance().publish("tryit-try-login", { - "client-id": clientId - }); - window.open(asgardeoLoginPlaygroundURL+"?client_id="+clientId+"&org="+tenant); - } } - data-testid={ `${ testId }-playground-button` } - > - Try Login - - - ); - } - - return null; - }, - getOverriddenDescription: (clientId: string, tenantDomain: string, _templateName: string) => { - if (clientId === getTryItClientId(tenantDomain)){ - return ( -
- - You can try out different login flows of Asgardeo with our Try It app. - - ) } - trigger={ ( - - - You can try out different login flows of Asgardeo with our Try It app. - - - ) } - /> -
- ); - } - - return null; - }, - getOverriddenImage: (clientId: string, tenantDomain: string) => { - if(clientId === getTryItClientId(tenantDomain)) { - return ( - - ); - } - - return null; - }, - getOverriddenTab: ( - clientId: string, - tabName: ApplicationTabTypes, - defaultComponent: ReactElement, - application: ApplicationInterface, - tenantDomain: string, - _onUpdate?:(id: string) => void, - _readOnly?:boolean - ) => { - if (clientId === getTryItClientId(tenantDomain) && tabName === ApplicationTabTypes.GENERAL) { - return ( - - ); - } - - if (clientId === getTryItClientId(tenantDomain) && tabName === ApplicationTabTypes.USER_ATTRIBUTES){ - return ( - - -
- User Attributes - - User attributes that are allowed to be shared with this application. - -
-
-
- ); - } - - return defaultComponent; - }, + getActions: () => null, + getOverriddenDescription: () => null, + getOverriddenImage: () => null, + getOverriddenTab: () => null, getStrongAuthenticationFlowTabIndex: ( clientId: string, tenantDomain: string diff --git a/features/admin.extensions.v1/configs/models/application.ts b/features/admin.extensions.v1/configs/models/application.ts index 50296cf0fe5..029f22c9044 100644 --- a/features/admin.extensions.v1/configs/models/application.ts +++ b/features/admin.extensions.v1/configs/models/application.ts @@ -93,7 +93,6 @@ export interface ApplicationConfig { getTabPanelReadOnlyStatus: (tabPanelName: string, application: ApplicationInterface) => boolean; isTabEnabledForApp: (clientId: string, tabType: ApplicationTabTypes, tenantDomain: string) => boolean; getActions: ( - applicationId: string, clientId: string, tenant: string, testId: string diff --git a/features/admin.organizations.v1/api/use-shared-organizations.ts b/features/admin.organizations.v1/api/use-shared-organizations.ts new file mode 100644 index 00000000000..53347a16ad0 --- /dev/null +++ b/features/admin.organizations.v1/api/use-shared-organizations.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import useRequest, { + RequestConfigInterface, + RequestErrorInterface, + RequestResultInterface +} from "@wso2is/admin.core.v1/hooks/use-request"; +import { store } from "@wso2is/admin.core.v1/store"; +import { HttpMethods } from "@wso2is/core/models"; +import { OrganizationListInterface } from "../models/organizations"; + +/** + * Hook to get the list of organizations which the application is shared to. + * + * @param applicationId - ID of the application. + * @param shouldFetch - Should fetch the data. + * @returns SWR response object containing the data, error, isValidating, mutate. + */ +const useSharedOrganizations = < + Data = OrganizationListInterface, + Error = RequestErrorInterface>( + applicationId: string, + shouldFetch: boolean + ): RequestResultInterface => { + const requestConfig: RequestConfigInterface = { + headers: { + "Accept": "application/json", + "Content-Type": "application/json" + }, + method: HttpMethods.GET, + url: `${store.getState().config.endpoints.applications}/${applicationId}/share` + }; + + const { + data, + error, + isValidating, + isLoading, + mutate + } = useRequest(shouldFetch ? requestConfig : null, { + shouldRetryOnError: false + }); + + return { + data, + error, + isLoading, + isValidating, + mutate + }; +}; + +export default useSharedOrganizations; From 2282ff9cd8d046e3a27b54d0248086a1ee8e5e3e Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Wed, 4 Sep 2024 21:16:14 +0530 Subject: [PATCH 2/7] Fix build failure --- features/admin.applications.v1/pages/application-edit.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/features/admin.applications.v1/pages/application-edit.tsx b/features/admin.applications.v1/pages/application-edit.tsx index ba0cb6ccdcd..a0a05b01fcc 100755 --- a/features/admin.applications.v1/pages/application-edit.tsx +++ b/features/admin.applications.v1/pages/application-edit.tsx @@ -584,7 +584,6 @@ const ApplicationEditPage: FunctionComponent = ( <> { applicationConfig.editApplication.getActions( - application?.id, inboundProtocolConfigs?.oidc?.clientId, tenantDomain, componentId From 8013d600b0730169cfb51f2f534b8ed0b4993543 Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Thu, 5 Sep 2024 10:53:15 +0530 Subject: [PATCH 3/7] Add changeset --- .changeset/two-crews-poke.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/two-crews-poke.md diff --git a/.changeset/two-crews-poke.md b/.changeset/two-crews-poke.md new file mode 100644 index 00000000000..51ae0842189 --- /dev/null +++ b/.changeset/two-crews-poke.md @@ -0,0 +1,7 @@ +--- +"@wso2is/admin.organizations.v1": patch +"@wso2is/admin.applications.v1": patch +"@wso2is/admin.extensions.v1": patch +--- + +Fix state delay in shared application tab in application edit page From e7898daf7f81840061fee78c300b326af9b5d712 Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Thu, 5 Sep 2024 11:31:46 +0530 Subject: [PATCH 4/7] Fix issue with users domain dropdown count reset --- features/admin.users.v1/pages/users.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/admin.users.v1/pages/users.tsx b/features/admin.users.v1/pages/users.tsx index 1a84ad72c1d..bcd0b37392d 100644 --- a/features/admin.users.v1/pages/users.tsx +++ b/features/admin.users.v1/pages/users.tsx @@ -32,9 +32,9 @@ import { history } from "@wso2is/admin.core.v1"; import { userstoresConfig } from "@wso2is/admin.extensions.v1"; +import { userConfig } from "@wso2is/admin.extensions.v1/configs"; import FeatureGateConstants from "@wso2is/admin.feature-gate.v1/constants/feature-gate-constants"; import { FeatureStatusLabel } from "@wso2is/admin.feature-gate.v1/models/feature-status"; -import { userConfig } from "@wso2is/admin.extensions.v1/configs"; import { useGetCurrentOrganizationType } from "@wso2is/admin.organizations.v1/hooks/use-get-organization-type"; import { ConnectorPropertyInterface, @@ -574,6 +574,7 @@ const UsersPage: FunctionComponent = ( setSelectedUserStore(data.value as string); } setListOffset(0); + setListItemLimit(UIConstants.DEFAULT_RESOURCE_LIST_ITEM_LIMIT); }; const onUserDelete = (): void => { From 70ab2d6c424442532ce4286efc293651da772657 Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Thu, 5 Sep 2024 11:46:38 +0530 Subject: [PATCH 5/7] Add updated changeset --- .changeset/pink-bats-begin.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/pink-bats-begin.md diff --git a/.changeset/pink-bats-begin.md b/.changeset/pink-bats-begin.md new file mode 100644 index 00000000000..608b6f295c5 --- /dev/null +++ b/.changeset/pink-bats-begin.md @@ -0,0 +1,8 @@ +--- +"@wso2is/admin.organizations.v1": patch +"@wso2is/admin.applications.v1": patch +"@wso2is/admin.extensions.v1": patch +"@wso2is/admin.users.v1": patch +--- + +Fix state delay in shared application tab in application edit page From cb2e8855899f3719827c62306a141aca4160b98c Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Thu, 5 Sep 2024 11:55:09 +0530 Subject: [PATCH 6/7] Remove old changeset --- .changeset/two-crews-poke.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .changeset/two-crews-poke.md diff --git a/.changeset/two-crews-poke.md b/.changeset/two-crews-poke.md deleted file mode 100644 index 51ae0842189..00000000000 --- a/.changeset/two-crews-poke.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@wso2is/admin.organizations.v1": patch -"@wso2is/admin.applications.v1": patch -"@wso2is/admin.extensions.v1": patch ---- - -Fix state delay in shared application tab in application edit page From ed91e314c3296242d77c5c236e20024cb881d48b Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Thu, 5 Sep 2024 13:53:01 +0530 Subject: [PATCH 7/7] Add null check --- .../components/forms/share-application-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/admin.applications.v1/components/forms/share-application-form.tsx b/features/admin.applications.v1/components/forms/share-application-form.tsx index 4e7e75aa824..c2b2713e4eb 100644 --- a/features/admin.applications.v1/components/forms/share-application-form.tsx +++ b/features/admin.applications.v1/components/forms/share-application-form.tsx @@ -144,7 +144,7 @@ export const ApplicationShareForm: FunctionComponent