From 552bbff99cc61d192f5bc1569a6b46082e677c37 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Sat, 28 Jun 2025 03:01:02 +0500 Subject: [PATCH] Search in Publish Permission Dialog --- .../lowcoder/src/api/applicationApi.ts | 5 + .../PermissionDialog/Permission.tsx | 140 +++++++++++------- .../src/constants/applicationConstants.ts | 14 +- .../packages/lowcoder/src/i18n/locales/en.ts | 1 + .../lowcoder/src/util/pagination/axios.ts | 16 ++ 5 files changed, 118 insertions(+), 58 deletions(-) diff --git a/client/packages/lowcoder/src/api/applicationApi.ts b/client/packages/lowcoder/src/api/applicationApi.ts index 2411b50d80..8ed818b371 100644 --- a/client/packages/lowcoder/src/api/applicationApi.ts +++ b/client/packages/lowcoder/src/api/applicationApi.ts @@ -99,6 +99,7 @@ class ApplicationApi extends Api { static publicToMarketplaceURL = (applicationId: string) => `/applications/${applicationId}/public-to-marketplace`; static getMarketplaceAppURL = (applicationId: string) => `/applications/${applicationId}/view_marketplace`; static setAppEditingStateURL = (applicationId: string) => `/applications/editState/${applicationId}`; + static getAvailableGroupsMembersURL = (applicationId: string) => `/applications/${applicationId}/groups-members/available`; static serverSettingsURL = () => `/serverSettings`; static fetchHomeData(request: HomeDataPayload): AxiosPromise { @@ -217,6 +218,10 @@ class ApplicationApi extends Api { }); } + static getAvailableGroupsMembers(applicationId: string, search: string): AxiosPromise { + return Api.get(ApplicationApi.getAvailableGroupsMembersURL(applicationId), {search}) + } + /** * set app as public */ diff --git a/client/packages/lowcoder/src/components/PermissionDialog/Permission.tsx b/client/packages/lowcoder/src/components/PermissionDialog/Permission.tsx index 82ea45beb9..6425d3afc6 100644 --- a/client/packages/lowcoder/src/components/PermissionDialog/Permission.tsx +++ b/client/packages/lowcoder/src/components/PermissionDialog/Permission.tsx @@ -3,16 +3,15 @@ import { CloseIcon, CommonTextLabel, CustomSelect, + Search, TacoButton, } from "lowcoder-design"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, useCallback } from "react"; import styled from "styled-components"; +import { debounce } from "lodash"; import ProfileImage from "pages/common/profileImage"; -import { useDispatch, useSelector } from "react-redux"; -import { fetchGroupsAction, fetchOrgUsersAction } from "redux/reduxActions/orgActions"; -import { getOrgGroups, getOrgUsers } from "redux/selectors/orgSelectors"; -import { OrgGroup, OrgUser } from "constants/orgConstants"; -import { ApplicationPermissionType, ApplicationRoleType } from "constants/applicationConstants"; +import { useSelector } from "react-redux"; +import { ApplicationPermissionType, ApplicationRoleType, GroupsMembersPermission } from "constants/applicationConstants"; import { PermissionItemName, RoleSelectOption, @@ -27,6 +26,8 @@ import { getUser } from "redux/selectors/usersSelectors"; import { EmptyContent } from "pages/common/styledComponent"; import { trans } from "i18n"; import { PermissionItem } from "./PermissionList"; +import { currentApplication } from "@lowcoder-ee/redux/selectors/applicationSelector"; +import { fetchAvailableGroupsMembers } from "@lowcoder-ee/util/pagination/axios"; const AddAppUserContent = styled.div` display: flex; @@ -86,8 +87,7 @@ const PermissionSelectWrapper = styled.div` padding: 4px 8px; margin-top: 8px; background: #fdfdfd; - outline: 1px solid #d7d9e0; - border-radius: 4px; + outline: 1px dashed #d7d9e0; .ant-select { font-size: 13px; @@ -95,11 +95,11 @@ const PermissionSelectWrapper = styled.div` } &:hover { - outline: 1px solid #8b8fa3; + outline: 1px dashed #8b8fa3; } &:focus-within { - outline: 1px solid #315efb; + outline: 1px dashed rgb(203, 212, 245); border-radius: 4px; box-shadow: 0 0 0 3px rgb(24 144 255 / 20%); } @@ -199,48 +199,34 @@ type PermissionAddEntity = { key: string; }; -/** - * compose users and groups's permissions, filter the data - * - * @param orgGroups groups - * @param orgUsers users - * @param currentUser currentUser - * @param filterItems filterItems - */ +function isGroup(data: GroupsMembersPermission) { + return data?.type === "Group" +} + function getPermissionOptionView( - orgGroups: OrgGroup[], - orgUsers: OrgUser[], - currentUser: User, + groupsMembers: GroupsMembersPermission[], filterItems: PermissionItem[] ): AddAppOptionView[] { - let permissionViews: AddAppOptionView[] = orgGroups.map((group) => { + + let permissionsViews = groupsMembers?.map((user) => { return { - type: "GROUP", - id: group.groupId, - name: group.groupName, - }; - }); - permissionViews = permissionViews.concat( - orgUsers.map((user) => { - return { - type: "USER", - id: user.userId, - name: user.name, - avatarUrl: user.avatarUrl, - }; - }) - ); - permissionViews = permissionViews.filter( - (v) => - !filterItems.find((i) => i.id === v.id && i.type === v.type) && - !(v.type === "USER" && v.id === currentUser.id) + type: user.type as ApplicationPermissionType, + id: isGroup(user) ? user.data.groupId : user.data.userId, + name: isGroup(user) ? user.data.groupName : user.data.name, + ...(isGroup(user) ? {} : { avatarUrl: user.data.avatarUrl }) + } + }) + + permissionsViews = permissionsViews.filter((v) => + !filterItems.find((i) => i.id === v.id && i.type === v.type) ); - return permissionViews; + + return permissionsViews.filter((v) => v.id && v.name) as AddAppOptionView[]; } function PermissionSelectorOption(props: { optionView: AddAppOptionView }) { const { optionView } = props; - const groupIcon = optionView.type === "GROUP" && ( + const groupIcon = optionView.type === "Group" && ( ); return ( @@ -258,7 +244,7 @@ function PermissionSelectorOption(props: { optionView: AddAppOptionView }) { function PermissionSelectorLabel(props: { view: AddAppOptionView }) { const { view } = props; - const groupIcon = view.type === "GROUP" && ( + const groupIcon = view.type === "Group" && ( ); return ( @@ -309,12 +295,52 @@ const PermissionSelector = (props: { filterItems: PermissionItem[]; supportRoles: { label: string; value: PermissionRole }[]; }) => { - const orgGroups = useSelector(getOrgGroups); - const orgUsers = useSelector(getOrgUsers); const { selectedItems, setSelectRole, setSelectedItems, user } = props; - const optionViews = getPermissionOptionView(orgGroups, orgUsers, user, props.filterItems); const [roleSelectVisible, setRoleSelectVisible] = useState(false); const selectRef = useRef(null); + const [optionViews, setOptionViews] = useState() + const [searchValue, setSearchValue] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const application = useSelector(currentApplication) + + const debouncedUserSearch = useCallback( + debounce((searchTerm: string) => { + if (!application) return; + + setIsLoading(true); + fetchAvailableGroupsMembers(application.applicationId, searchTerm).then(res => { + if(res.success) { + setOptionViews(getPermissionOptionView(res.data, props.filterItems)) + } + setIsLoading(false); + }).catch(() => { + setIsLoading(false); + }); + }, 500), + [application, props.filterItems] + ); + + useEffect(() => { + debouncedUserSearch(searchValue); + + return () => { + debouncedUserSearch.cancel(); + }; + }, [searchValue, debouncedUserSearch]); + + useEffect(() => { + if (!application) return; + + setIsLoading(true); + fetchAvailableGroupsMembers(application.applicationId, "").then(res => { + if(res.success) { + setOptionViews(getPermissionOptionView(res.data, props.filterItems)) + } + setIsLoading(false); + }).catch(() => { + setIsLoading(false); + }); + }, [application, props.filterItems]); useEffect(() => { setRoleSelectVisible(selectedItems.length > 0); @@ -325,12 +351,18 @@ const PermissionSelector = (props: { return ( <> + setSearchValue(e.target.value)} + /> document.getElementById("add-app-user-permission-dropdown")!} optionLabelProp="label" tagRender={PermissionTagRender} @@ -350,7 +382,7 @@ const PermissionSelector = (props: { setSelectedItems(selectedItems.filter((item) => item.key !== option.key)); }} > - {optionViews.map((view) => { + {optionViews?.map((view) => { return ( void; }) => { const { onCancel } = props; - const dispatch = useDispatch(); const user = useSelector(getUser); const [selectRole, setSelectRole] = useState("viewer"); const [selectedItems, setSelectedItems] = useState([]); - useEffect(() => { - dispatch(fetchOrgUsersAction(user.currentOrgId)); - dispatch(fetchGroupsAction(user.currentOrgId)); - }, []); - return ( @@ -426,10 +452,10 @@ export const Permission = (props: { buttonType="primary" onClick={() => { const uids = selectedItems - .filter((item) => item.type === "USER") + .filter((item) => item.type === "User") .map((item) => item.id); const gids = selectedItems - .filter((item) => item.type === "GROUP") + .filter((item) => item.type === "Group") .map((item) => item.id); if (uids.length === 0 && gids.length === 0) { onCancel(); diff --git a/client/packages/lowcoder/src/constants/applicationConstants.ts b/client/packages/lowcoder/src/constants/applicationConstants.ts index f29dce24b6..f685eeb8e6 100644 --- a/client/packages/lowcoder/src/constants/applicationConstants.ts +++ b/client/packages/lowcoder/src/constants/applicationConstants.ts @@ -62,7 +62,7 @@ export const AppUILayoutType: Record = { export type ApplicationDSLType = "editing" | "published" | "view_marketplace"; export type ApplicationRoleType = "viewer" | "editor" | "owner"; -export type ApplicationPermissionType = "USER" | "GROUP" | "ORG_ADMIN"; +export type ApplicationPermissionType = "User" | "Group" | "ORG_ADMIN" | "USER" | "GROUP"; export interface ApplicationExtra { moduleHeight?: number; @@ -70,6 +70,18 @@ export interface ApplicationExtra { layers?: boolean; } +export type GroupsMembersPermission = { + type: string + data: { + groupGid?: string + groupId?: string + userId?: string + groupName?: string + name?: string + avatarUrl?: string + } +} + export interface ApplicationMeta { name: string; applicationType: AppTypeEnum; diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 44f5f4b1dd..470e8ec178 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -4010,6 +4010,7 @@ export const en = { "orgName": "{orgName} admins", "addMember": "Add members", "addPermissionPlaceholder": "Please enter a name to search members", + "selectedUsersAndGroups":"Selected Users and Groups", "searchMemberOrGroup": "Search for members or groups: ", "addPermissionErrorMessage": "Failed to add permission, {message}", "copyModalTitle": 'Clone "{name}"', diff --git a/client/packages/lowcoder/src/util/pagination/axios.ts b/client/packages/lowcoder/src/util/pagination/axios.ts index d03bf1800c..e59ccbfec1 100644 --- a/client/packages/lowcoder/src/util/pagination/axios.ts +++ b/client/packages/lowcoder/src/util/pagination/axios.ts @@ -102,6 +102,22 @@ export const fetchGroupUsrPagination = async (request: fetchGroupUserRequestType } } +export const fetchAvailableGroupsMembers = async (applicationId: string, search: string) => { + try{ + const response = await ApplicationApi.getAvailableGroupsMembers(applicationId, search) + return { + success: true, + data: response.data.data + } + } catch (error: any) { + console.error('Failed to fetch data: ', error) + return { + success: false, + error: error + } + } +} + export const fetchOrgUsrPagination = async (request: fetchOrgUserRequestType)=> { try { const response = await OrgApi.fetchOrgUsersPagination(request); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy