Content-Length: 941683 | pFad | http://github.com/lowcoder-org/lowcoder/commit/552bbff99cc61d192f5bc1569a6b46082e677c37

4E Search in Publish Permission Dialog · lowcoder-org/lowcoder@552bbff · GitHub
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)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/lowcoder-org/lowcoder/commit/552bbff99cc61d192f5bc1569a6b46082e677c37

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy