Skip to content

Commit 552bbff

Browse files
committed
Search in Publish Permission Dialog
1 parent 48d6d7b commit 552bbff

File tree

5 files changed

+118
-58
lines changed

5 files changed

+118
-58
lines changed

client/packages/lowcoder/src/api/applicationApi.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class ApplicationApi extends Api {
9999
static publicToMarketplaceURL = (applicationId: string) => `/applications/${applicationId}/public-to-marketplace`;
100100
static getMarketplaceAppURL = (applicationId: string) => `/applications/${applicationId}/view_marketplace`;
101101
static setAppEditingStateURL = (applicationId: string) => `/applications/editState/${applicationId}`;
102+
static getAvailableGroupsMembersURL = (applicationId: string) => `/applications/${applicationId}/groups-members/available`;
102103
static serverSettingsURL = () => `/serverSettings`;
103104

104105
static fetchHomeData(request: HomeDataPayload): AxiosPromise<HomeDataResponse> {
@@ -217,6 +218,10 @@ class ApplicationApi extends Api {
217218
});
218219
}
219220

221+
static getAvailableGroupsMembers(applicationId: string, search: string): AxiosPromise<any> {
222+
return Api.get(ApplicationApi.getAvailableGroupsMembersURL(applicationId), {search})
223+
}
224+
220225
/**
221226
* set app as public
222227
*/

client/packages/lowcoder/src/components/PermissionDialog/Permission.tsx

Lines changed: 83 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ import {
33
CloseIcon,
44
CommonTextLabel,
55
CustomSelect,
6+
Search,
67
TacoButton,
78
} from "lowcoder-design";
8-
import { useEffect, useRef, useState } from "react";
9+
import { useEffect, useRef, useState, useCallback } from "react";
910
import styled from "styled-components";
11+
import { debounce } from "lodash";
1012
import ProfileImage from "pages/common/profileImage";
11-
import { useDispatch, useSelector } from "react-redux";
12-
import { fetchGroupsAction, fetchOrgUsersAction } from "redux/reduxActions/orgActions";
13-
import { getOrgGroups, getOrgUsers } from "redux/selectors/orgSelectors";
14-
import { OrgGroup, OrgUser } from "constants/orgConstants";
15-
import { ApplicationPermissionType, ApplicationRoleType } from "constants/applicationConstants";
13+
import { useSelector } from "react-redux";
14+
import { ApplicationPermissionType, ApplicationRoleType, GroupsMembersPermission } from "constants/applicationConstants";
1615
import {
1716
PermissionItemName,
1817
RoleSelectOption,
@@ -27,6 +26,8 @@ import { getUser } from "redux/selectors/usersSelectors";
2726
import { EmptyContent } from "pages/common/styledComponent";
2827
import { trans } from "i18n";
2928
import { PermissionItem } from "./PermissionList";
29+
import { currentApplication } from "@lowcoder-ee/redux/selectors/applicationSelector";
30+
import { fetchAvailableGroupsMembers } from "@lowcoder-ee/util/pagination/axios";
3031

3132
const AddAppUserContent = styled.div`
3233
display: flex;
@@ -86,20 +87,19 @@ const PermissionSelectWrapper = styled.div`
8687
padding: 4px 8px;
8788
margin-top: 8px;
8889
background: #fdfdfd;
89-
outline: 1px solid #d7d9e0;
90-
border-radius: 4px;
90+
outline: 1px dashed #d7d9e0;
9191
9292
.ant-select {
9393
font-size: 13px;
9494
line-height: 13px;
9595
}
9696
9797
&:hover {
98-
outline: 1px solid #8b8fa3;
98+
outline: 1px dashed #8b8fa3;
9999
}
100100
101101
&:focus-within {
102-
outline: 1px solid #315efb;
102+
outline: 1px dashed rgb(203, 212, 245);
103103
border-radius: 4px;
104104
box-shadow: 0 0 0 3px rgb(24 144 255 / 20%);
105105
}
@@ -199,48 +199,34 @@ type PermissionAddEntity = {
199199
key: string;
200200
};
201201

202-
/**
203-
* compose users and groups's permissions, filter the data
204-
*
205-
* @param orgGroups groups
206-
* @param orgUsers users
207-
* @param currentUser currentUser
208-
* @param filterItems filterItems
209-
*/
202+
function isGroup(data: GroupsMembersPermission) {
203+
return data?.type === "Group"
204+
}
205+
210206
function getPermissionOptionView(
211-
orgGroups: OrgGroup[],
212-
orgUsers: OrgUser[],
213-
currentUser: User,
207+
groupsMembers: GroupsMembersPermission[],
214208
filterItems: PermissionItem[]
215209
): AddAppOptionView[] {
216-
let permissionViews: AddAppOptionView[] = orgGroups.map((group) => {
210+
211+
let permissionsViews = groupsMembers?.map((user) => {
217212
return {
218-
type: "GROUP",
219-
id: group.groupId,
220-
name: group.groupName,
221-
};
222-
});
223-
permissionViews = permissionViews.concat(
224-
orgUsers.map((user) => {
225-
return {
226-
type: "USER",
227-
id: user.userId,
228-
name: user.name,
229-
avatarUrl: user.avatarUrl,
230-
};
231-
})
232-
);
233-
permissionViews = permissionViews.filter(
234-
(v) =>
235-
!filterItems.find((i) => i.id === v.id && i.type === v.type) &&
236-
!(v.type === "USER" && v.id === currentUser.id)
213+
type: user.type as ApplicationPermissionType,
214+
id: isGroup(user) ? user.data.groupId : user.data.userId,
215+
name: isGroup(user) ? user.data.groupName : user.data.name,
216+
...(isGroup(user) ? {} : { avatarUrl: user.data.avatarUrl })
217+
}
218+
})
219+
220+
permissionsViews = permissionsViews.filter((v) =>
221+
!filterItems.find((i) => i.id === v.id && i.type === v.type)
237222
);
238-
return permissionViews;
223+
224+
return permissionsViews.filter((v) => v.id && v.name) as AddAppOptionView[];
239225
}
240226

241227
function PermissionSelectorOption(props: { optionView: AddAppOptionView }) {
242228
const { optionView } = props;
243-
const groupIcon = optionView.type === "GROUP" && (
229+
const groupIcon = optionView.type === "Group" && (
244230
<StyledGroupIcon $color={getInitialsAndColorCode(optionView.name)[1]} />
245231
);
246232
return (
@@ -258,7 +244,7 @@ function PermissionSelectorOption(props: { optionView: AddAppOptionView }) {
258244

259245
function PermissionSelectorLabel(props: { view: AddAppOptionView }) {
260246
const { view } = props;
261-
const groupIcon = view.type === "GROUP" && (
247+
const groupIcon = view.type === "Group" && (
262248
<StyledGroupIcon $color={getInitialsAndColorCode(view.name)[1]} $side={9} />
263249
);
264250
return (
@@ -309,12 +295,52 @@ const PermissionSelector = (props: {
309295
filterItems: PermissionItem[];
310296
supportRoles: { label: string; value: PermissionRole }[];
311297
}) => {
312-
const orgGroups = useSelector(getOrgGroups);
313-
const orgUsers = useSelector(getOrgUsers);
314298
const { selectedItems, setSelectRole, setSelectedItems, user } = props;
315-
const optionViews = getPermissionOptionView(orgGroups, orgUsers, user, props.filterItems);
316299
const [roleSelectVisible, setRoleSelectVisible] = useState(false);
317300
const selectRef = useRef<HTMLDivElement>(null);
301+
const [optionViews, setOptionViews] = useState<AddAppOptionView[]>()
302+
const [searchValue, setSearchValue] = useState("");
303+
const [isLoading, setIsLoading] = useState(false);
304+
const application = useSelector(currentApplication)
305+
306+
const debouncedUserSearch = useCallback(
307+
debounce((searchTerm: string) => {
308+
if (!application) return;
309+
310+
setIsLoading(true);
311+
fetchAvailableGroupsMembers(application.applicationId, searchTerm).then(res => {
312+
if(res.success) {
313+
setOptionViews(getPermissionOptionView(res.data, props.filterItems))
314+
}
315+
setIsLoading(false);
316+
}).catch(() => {
317+
setIsLoading(false);
318+
});
319+
}, 500),
320+
[application, props.filterItems]
321+
);
322+
323+
useEffect(() => {
324+
debouncedUserSearch(searchValue);
325+
326+
return () => {
327+
debouncedUserSearch.cancel();
328+
};
329+
}, [searchValue, debouncedUserSearch]);
330+
331+
useEffect(() => {
332+
if (!application) return;
333+
334+
setIsLoading(true);
335+
fetchAvailableGroupsMembers(application.applicationId, "").then(res => {
336+
if(res.success) {
337+
setOptionViews(getPermissionOptionView(res.data, props.filterItems))
338+
}
339+
setIsLoading(false);
340+
}).catch(() => {
341+
setIsLoading(false);
342+
});
343+
}, [application, props.filterItems]);
318344

319345
useEffect(() => {
320346
setRoleSelectVisible(selectedItems.length > 0);
@@ -325,12 +351,18 @@ const PermissionSelector = (props: {
325351

326352
return (
327353
<>
354+
<Search
355+
placeholder={trans("home.addPermissionPlaceholder")}
356+
value={searchValue}
357+
onChange={(e) => setSearchValue(e.target.value)}
358+
/>
328359
<PermissionSelectWrapper>
329360
<AddPermissionsSelect
330361
open
331362
ref={selectRef}
332-
placeholder={trans("home.addPermissionPlaceholder")}
363+
placeholder={trans("home.selectedUsersAndGroups")}
333364
mode="multiple"
365+
showSearch={false}
334366
getPopupContainer={() => document.getElementById("add-app-user-permission-dropdown")!}
335367
optionLabelProp="label"
336368
tagRender={PermissionTagRender}
@@ -350,7 +382,7 @@ const PermissionSelector = (props: {
350382
setSelectedItems(selectedItems.filter((item) => item.key !== option.key));
351383
}}
352384
>
353-
{optionViews.map((view) => {
385+
{optionViews?.map((view) => {
354386
return (
355387
<CustomSelect.Option
356388
key={`${view.type}-${view.id}`}
@@ -395,16 +427,10 @@ export const Permission = (props: {
395427
addPermission: (userIds: string[], groupIds: string[], role: string) => void;
396428
}) => {
397429
const { onCancel } = props;
398-
const dispatch = useDispatch();
399430
const user = useSelector(getUser);
400431
const [selectRole, setSelectRole] = useState<ApplicationRoleType>("viewer");
401432
const [selectedItems, setSelectedItems] = useState<PermissionAddEntity[]>([]);
402433

403-
useEffect(() => {
404-
dispatch(fetchOrgUsersAction(user.currentOrgId));
405-
dispatch(fetchGroupsAction(user.currentOrgId));
406-
}, []);
407-
408434
return (
409435
<AddAppUserContent>
410436
<CommonTextLabel style={{ marginTop: "16px" }}>
@@ -426,10 +452,10 @@ export const Permission = (props: {
426452
buttonType="primary"
427453
onClick={() => {
428454
const uids = selectedItems
429-
.filter((item) => item.type === "USER")
455+
.filter((item) => item.type === "User")
430456
.map((item) => item.id);
431457
const gids = selectedItems
432-
.filter((item) => item.type === "GROUP")
458+
.filter((item) => item.type === "Group")
433459
.map((item) => item.id);
434460
if (uids.length === 0 && gids.length === 0) {
435461
onCancel();

client/packages/lowcoder/src/constants/applicationConstants.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,26 @@ export const AppUILayoutType: Record<AppTypeEnum, UiLayoutType> = {
6262

6363
export type ApplicationDSLType = "editing" | "published" | "view_marketplace";
6464
export type ApplicationRoleType = "viewer" | "editor" | "owner";
65-
export type ApplicationPermissionType = "USER" | "GROUP" | "ORG_ADMIN";
65+
export type ApplicationPermissionType = "User" | "Group" | "ORG_ADMIN" | "USER" | "GROUP";
6666

6767
export interface ApplicationExtra {
6868
moduleHeight?: number;
6969
moduleWidth?: number;
7070
layers?: boolean;
7171
}
7272

73+
export type GroupsMembersPermission = {
74+
type: string
75+
data: {
76+
groupGid?: string
77+
groupId?: string
78+
userId?: string
79+
groupName?: string
80+
name?: string
81+
avatarUrl?: string
82+
}
83+
}
84+
7385
export interface ApplicationMeta {
7486
name: string;
7587
applicationType: AppTypeEnum;

client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4010,6 +4010,7 @@ export const en = {
40104010
"orgName": "{orgName} admins",
40114011
"addMember": "Add members",
40124012
"addPermissionPlaceholder": "Please enter a name to search members",
4013+
"selectedUsersAndGroups":"Selected Users and Groups",
40134014
"searchMemberOrGroup": "Search for members or groups: ",
40144015
"addPermissionErrorMessage": "Failed to add permission, {message}",
40154016
"copyModalTitle": 'Clone "{name}"',

client/packages/lowcoder/src/util/pagination/axios.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,22 @@ export const fetchGroupUsrPagination = async (request: fetchGroupUserRequestType
102102
}
103103
}
104104

105+
export const fetchAvailableGroupsMembers = async (applicationId: string, search: string) => {
106+
try{
107+
const response = await ApplicationApi.getAvailableGroupsMembers(applicationId, search)
108+
return {
109+
success: true,
110+
data: response.data.data
111+
}
112+
} catch (error: any) {
113+
console.error('Failed to fetch data: ', error)
114+
return {
115+
success: false,
116+
error: error
117+
}
118+
}
119+
}
120+
105121
export const fetchOrgUsrPagination = async (request: fetchOrgUserRequestType)=> {
106122
try {
107123
const response = await OrgApi.fetchOrgUsersPagination(request);

0 commit comments

Comments
 (0)
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