Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds getChannelUsersQuery method, bumps to @esri/arcgis-rest-port… #1210

Merged
merged 9 commits into from
Sep 22, 2023
22 changes: 11 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@commitlint/prompt": "^11.0.0",
"@esri/arcgis-rest-auth": "^3.1.1",
"@esri/arcgis-rest-feature-layer": "^3.4.3",
"@esri/arcgis-rest-portal": "^3.5.0",
"@esri/arcgis-rest-portal": "^3.7.0",
"@esri/arcgis-rest-request": "^3.1.1",
"@esri/arcgis-rest-service-admin": "^3.6.0",
"@esri/arcgis-rest-types": "^3.1.1",
Expand Down
176 changes: 173 additions & 3 deletions packages/common/src/discussions/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { IGroup, IItem } from "@esri/arcgis-rest-types";
import { IHubContent, IHubItemEntity } from "../core";
import { CANNOT_DISCUSS } from "./constants";
import { IRequestOptions } from "@esri/arcgis-rest-request";
import { updateItem, updateGroup } from "@esri/arcgis-rest-portal";
import { IUserRequestOptions } from "@esri/arcgis-rest-auth";
import {
AclCategory,
AclSubCategory,
IChannel,
IChannelAclPermission,
SharingAccess,
} from "./api/types";
import {
IFilter,
IHubSearchOptions,
IHubSearchResponse,
IHubSearchResult,
IPredicate,
IQuery,
hubSearch,
} from "../search";

/**
* Utility to determine if a given IGroup, IItem, IHubContent, or IHubItemEntity
Expand Down Expand Up @@ -35,3 +48,160 @@ export function setDiscussableKeyword(
}
return updatedTypeKeywords;
}

/**
* Determines if the given channel is considered to be a `public` channel, supporting both
* legacy permissions and V2 ACL model.
* @param channel An IChannel record
* @returns true if the channel is considered `public`
*/
export function isPublicChannel(channel: IChannel): boolean {
return channel.channelAcl
? channel.channelAcl.some(
({ category }) => category === AclCategory.AUTHENTICATED_USER
)
: channel.access === SharingAccess.PUBLIC;
}

/**
* Determines if the given channel is considered to be an `org` channel, supporting both
* legacy permissions and V2 ACL model.
* @param channel An IChannel record
* @returns true if the channel is considered `org`
*/
export function isOrgChannel(channel: IChannel): boolean {
return channel.channelAcl
? !isPublicChannel(channel) &&
channel.channelAcl.some(
({ category, subCategory }) =>
category === AclCategory.ORG &&
subCategory === AclSubCategory.MEMBER
)
: channel.access === SharingAccess.ORG;
}

/**
* Determines if the given channel is considered to be a `private` channel, supporting both
* legacy permissions and V2 ACL model.
* @param channel An IChannel record
* @returns true if the channel is considered `private`
*/
export function isPrivateChannel(channel: IChannel): boolean {
return !isPublicChannel(channel) && !isOrgChannel(channel);
rweber-esri marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Determines the given channel's access, supporting both legacy permissions and V2 ACL
* model.
* @param channel An IChannel record
* @returns `public`, `org` or `private`
*/
export function getChannelAccess(channel: IChannel): SharingAccess {
let access = SharingAccess.PRIVATE;
if (isPublicChannel(channel)) {
access = SharingAccess.PUBLIC;
} else if (isOrgChannel(channel)) {
access = SharingAccess.ORG;
}
return access;
}

/**
* Returns an array of org ids configured for the channel, supporting both legacy permissions
* and V2 ACL model.
* @param channel An IChannel record
* @returns an array of org ids for the given channel
*/
export function getChannelOrgIds(channel: IChannel): string[] {
return channel.channelAcl
? channel.channelAcl.reduce(
(acc, permission) =>
permission.category === AclCategory.ORG &&
permission.subCategory === AclSubCategory.MEMBER
? [...acc, permission.key]
: acc,
[]
)
: channel.orgs;
}

/**
* Returns an array of group ids configured for the channel, supporting both legacy permissions
* and V2 ACL model.
* @param channel An IChannel record
* @returns an array of group ids for the given channel
*/
export function getChannelGroupIds(channel: IChannel): string[] {
return channel.channelAcl
? channel.channelAcl.reduce(
(acc, permission) =>
permission.category === AclCategory.GROUP &&
permission.subCategory === AclSubCategory.MEMBER
? [...acc, permission.key]
: acc,
[]
)
: channel.groups;
}

/**
* A utility method used to build an IQuery to search for users that are permitted to be at-mentioned for the given channel.
* @param input An array of strings to search for. Each string is mapped to `username` and `fullname`, filters as an OR condition
* @param channel An IChannel record
* @param currentUsername The currently authenticated user's username
* @param options An IHubSearchOptions object
* @returns a promise that resolves an IHubSearchResponse<IHubSearchResult>
*/
export function getChannelUsersQuery(
inputs: string[],
channel: IChannel,
currentUsername?: string
): IQuery {
const groupIds = getChannelGroupIds(channel);
const orgIds = getChannelOrgIds(channel);
const groupsPredicate = { group: groupIds };
let filters: IFilter[];
if (isPublicChannel(channel)) {
filters = [
{
operation: "OR",
predicates: [{ orgid: { from: "0", to: "{" } }],
},
];
} else if (isOrgChannel(channel)) {
const additional = groupIds.length ? [groupsPredicate] : [];
filters = [
{
operation: "OR",
predicates: [{ orgid: orgIds }, ...additional],
},
];
} else {
filters = [
{
operation: "AND",
predicates: [groupsPredicate],
},
];
}
if (currentUsername) {
filters.push({
operation: "AND",
predicates: [{ username: { not: currentUsername } }],
});
}
const query: IQuery = {
targetEntity: "communityUser",
filters: [
{
operation: "OR",
predicates: inputs.reduce<IPredicate[]>(
(acc, input) => [...acc, { username: input }, { fullname: input }],
[]
),
},
...filters,
],
};
return query;
}
Original file line number Diff line number Diff line change
Expand Up @@ -218,5 +218,9 @@ async function memberToSearchResult(
}
});

return enrichUserSearchResult(user, include, requestOptions);
return enrichUserSearchResult(
user,
["org.name as OrgName", ...include],
Copy link
Contributor Author

@rweber-esri rweber-esri Sep 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preserving the existing org enrichment when searching for group members to avoid a breaking change since enrichUserSearchResult needed to be refactored since new portalSearchUsers and communitySearchUsers do not want/need that enrichment

requestOptions
);
}
Loading