Skip to content

Commit

Permalink
feat(hub-common): _searchGroups via Filter<group>
Browse files Browse the repository at this point in the history
  • Loading branch information
dbouwman committed Nov 3, 2021
1 parent 7dae136 commit 496748b
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 32 deletions.
17 changes: 9 additions & 8 deletions packages/common/e2e/search-groups.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ fdescribe("SearchGroups:", () => {
const opts: IHubSearchOptions = {
apis: ["arcgisQA"],
};
const results = await _searchGroups(f, opts);
expect(results.groups.length).toBeGreaterThan(1);
const response = await _searchGroups(f, opts);
expect(response.results.length).toBeGreaterThan(1);
});
it("authd", async () => {
const orgName = "hubBasic";
Expand All @@ -40,8 +40,8 @@ fdescribe("SearchGroups:", () => {
sortField: "created",
sortOrder: "desc",
};
const results = await _searchGroups(f, opts);
expect(results.groups.length).toBe(10);
const response = await _searchGroups(f, opts);
expect(response.results.length).toBe(10);
});
it("typeKeyword", async () => {
const orgName = "hubBasic";
Expand All @@ -59,8 +59,8 @@ fdescribe("SearchGroups:", () => {
sortField: "created",
sortOrder: "desc",
};
const results = await _searchGroups(f, opts);
expect(results.groups.length).toBe(10);
const response = await _searchGroups(f, opts);
expect(response.results.length).toBe(10);
});
it("searchUserAccess", async () => {
const orgName = "hubBasic";
Expand All @@ -76,8 +76,9 @@ fdescribe("SearchGroups:", () => {
sortField: "created",
sortOrder: "desc",
};
const results = await _searchGroups(f, opts);
expect(results.groups.length).toBe(10);
const response = await _searchGroups(f, opts);
expect(response.results.length).toBe(10);
// response.results.map((g) => console.log(g.thumbnailUrl));
});
});
});
66 changes: 52 additions & 14 deletions packages/common/src/search/_searchGroups.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import { IGroup } from "@esri/arcgis-rest-portal";
import { IGroup, ISearchResult } from "@esri/arcgis-rest-portal";
import { Filter, IFacet, IHubSearchOptions } from "./types";
import { searchGroups as portalGroupSearch } from "@esri/arcgis-rest-portal";
import { convertPortalResponseToFacets, expandApis } from ".";
import {
convertPortalResponseToFacets,
expandApis,
mergeSearchResults,
} from ".";
import {
expandGroupFilter,
serializeGroupFilterForPortal,
} from "./group-utils";
import { UserSession } from "@esri/arcgis-rest-auth";
export interface IGroupSearchResult {
groups: IGroup[];
facets: IFacet[];
}

/**
* Search Groups using Filter<"group">
*
* Currently just returns ISearchResult<IGroup>
* @param filter
* @param options
* @returns
*/
export async function _searchGroups(
filter: Filter<"group">,
options: IHubSearchOptions
): Promise<IGroupSearchResult> {
): Promise<ISearchResult<IGroup>> {
// expand filter so we can serialize to either api
const expanded = expandGroupFilter(filter);

Expand All @@ -25,23 +38,48 @@ export async function _searchGroups(
}
const apis = expandApis(options.apis);
const so = serializeGroupFilterForPortal(expanded);
// pass auth forward
let token = "";
// if we have auth, pass it forward
// otherwise set the portal property
if (options.authentication) {
so.authentication = options.authentication;
const us: UserSession = options.authentication as UserSession;
token = us.token;
} else {
so.portal = `${apis[0].url}/sharing/rest`;
}
// Aggregations
if (options.aggregations?.length) {
so.countFields = options.aggregations.join(",");
so.countSize = 200;
}

if (options.num) {
so.num = options.num;
}

let portalUrl = "https://www.arcgis.com/sharing/rest";
if (so.authentication?.portal) {
portalUrl = so.authentication.portal;
}

const thumbnailify = (group: IGroup) => {
return addThumbnailUrl(portalUrl, group, token);
};

return portalGroupSearch(so).then((response) => {
// TODO: upgrade thumbnail url
const groups = response.results;
// Group Search does not support any aggregations
const facets = [] as IFacet[];
return { groups, facets };
// upgrade thumbnail url
response.results = response.results.map(thumbnailify);
return response;
});
}

// Need to decide how to handle adding the token is group is not public
function addThumbnailUrl(
portalUrl: string,
group: IGroup,
token?: string
): IGroup {
if (group.thumbnail) {
group.thumnailUrl = `${portalUrl}/community/groups/${group.id}/info/${group.thumbnail}`;
if (token) {
group.thumbnailUrl = `${group.thumbnailUrl}?token=${token}`;
}
}
return group;
}
22 changes: 12 additions & 10 deletions packages/common/src/search/group-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ export function expandGroupFilter(
const result = {} as IGroupFilterDefinition;
const dateProps = ["created", "modified"];
// Some properties should not get converted to MatchOptions
const specialProps = ["searchUserAccess", ...dateProps];
const specialProps = ["term", "searchUserAccess", ...dateProps];
// Do the conversion

Object.entries(filter).forEach(([key, value]) => {
// handle Matchish fields
if (!specialProps.includes(key)) {
// setProp side-steps typescript complaining
setProp(key, valueToMatchOptions(value) as IMatchOptions, result);
setProp(key, valueToMatchOptions(value), result);
}
// Handle date fields
if (dateProps.includes(key)) {
Expand All @@ -43,6 +43,10 @@ export function expandGroupFilter(
result.searchUserAccess = filter.searchUserAccess;
}

if (filter.term) {
result.term = filter.term;
}

return result;
}

Expand All @@ -55,15 +59,9 @@ export function serializeGroupFilterForPortal(
} as ISearchOptions;

const dateProps = ["created", "modified"];
const specialProps = [
"searchUserAccess",
"filterType",
"subFilters",
"term",
...dateProps,
];
const specialProps = ["term", "searchUserAccess", "filterType", ...dateProps];
Object.entries(filter).forEach(([key, value]) => {
// MatchOptions may go into either q or filter
// MatchOptions fields
if (!specialProps.includes(key)) {
result = mergeSearchOptions(
result,
Expand All @@ -84,5 +82,9 @@ export function serializeGroupFilterForPortal(
if (filter.hasOwnProperty("searchUserAccess")) {
result.searchUserAccess = filter.searchUserAccess;
}
// add the term
if (filter.term) {
result.q = `${filter.term} ${result.q}`.trim();
}
return result;
}
2 changes: 2 additions & 0 deletions packages/common/src/search/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UserSession } from "@esri/arcgis-rest-auth";
import { IGroup } from "@esri/arcgis-rest-types";
import { IHubContent, IModel } from "..";

/**
Expand Down Expand Up @@ -139,6 +140,7 @@ export interface IUserFilterDefinition {
}

export interface IGroupFilterDefinition {
term?: string;
access?: string | string[] | IMatchOptions;
created?: IDateRange<number> | IRelativeDate;
id?: string | string[] | IMatchOptions;
Expand Down
20 changes: 20 additions & 0 deletions packages/common/test/mocks/mock-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { UserSession } from "@esri/arcgis-rest-auth";

const TOMORROW = (function () {
const now = new Date();
now.setDate(now.getDate() + 1);
return now;
})();

export const MOCK_AUTH = new UserSession({
clientId: "clientId",
redirectUri: "https://example-app.com/redirect-uri",
token: "fake-token",
tokenExpires: TOMORROW,
refreshToken: "refreshToken",
refreshTokenExpires: TOMORROW,
refreshTokenTTL: 1440,
username: "casey",
password: "123456",
portal: "https://myorg.maps.arcgis.com/sharing/rest",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"query": "(Open) ",
"total": 2,
"start": 1,
"num": 20,
"nextStart": -1,
"results": [
{
"id": "7d9cc5e39a8f4c0aa29e04a473bf4703",
"title": "00 Demo OD Initiative Open Data Group",
"isInvitationOnly": false,
"owner": "dbouwman",
"description": "00 Demo OD Initiative Open Data Group",
"snippet": null,
"tags": [
"Hub Initiative Group",
"Open Data"
],
"typeKeywords": [],
"phone": null,
"sortField": "title",
"sortOrder": "asc",
"isViewOnly": false,
"isOpenData": true,
"featuredItemsId": null,
"thumbnail": "happy-gilmore.png",
"created": 1551991859000,
"modified": 1551991860000,
"access": "public",
"capabilities": [],
"isFav": false,
"isReadOnly": false,
"protected": true,
"autoJoin": false,
"notificationsEnabled": false,
"provider": null,
"providerGroupName": null,
"leavingDisallowed": false,
"hiddenMembers": false,
"displaySettings": {
"itemTypes": ""
},
"properties": null
},
{
"id": "8564992879dc48ed82780637f99074bf",
"title": "Alexandria Open Data Demo Content",
"isInvitationOnly": false,
"owner": "phammons_dcdev",
"description": "Use this group to organize the items that you want to share as part of your initiative. Shared items become available in your initiative's search results and only people who have access to these items will be able to find them. Members of the core team get access to shared items and can update them at any time. Certain cards, like the Gallery card, will automatically populate with shared items so that you don't have to search for them when choosing what you want to display on your site.<br /><br />Contact support with any questions related to this group or content management for your site.<br /><br /><strong>DO NOT DELETE THIS GROUP.</strong>",
"snippet": "Applications, maps, data, etc. shared with this group generates the Alexandria Open Data Demo content catalog.",
"tags": [
"Hub Group",
"Hub Content Group",
"Hub Site Group",
"Hub Initiative Group"
],
"typeKeywords": [],
"phone": null,
"sortField": "modified",
"sortOrder": "desc",
"isViewOnly": false,
"featuredItemsId": null,
"thumbnail": null,
"created": 1586893843000,
"modified": 1586893844000,
"access": "public",
"capabilities": [],
"isFav": false,
"isReadOnly": false,
"protected": true,
"autoJoin": false,
"notificationsEnabled": false,
"provider": null,
"providerGroupName": null,
"leavingDisallowed": false,
"hiddenMembers": false,
"displaySettings": {
"itemTypes": ""
},
"properties": null
}

]
}
67 changes: 67 additions & 0 deletions packages/common/test/search/_searchGroups.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
cloneObject,
Filter,
IHubSearchOptions,
_searchGroups,
} from "../../src";
import * as Portal from "@esri/arcgis-rest-portal";
import * as SimpleResponse from "../mocks/portal-groups-search/simple-response.json";
import { MOCK_AUTH } from "../mocks/mock-auth";

describe("_searchGroups:", () => {
it("defaults to ago prod", async () => {
const searchGroupsSpy = spyOn(Portal, "searchGroups").and.callFake(() => {
return Promise.resolve(cloneObject(SimpleResponse));
});
const f: Filter<"group"> = {
filterType: "group",
term: "water",
};
const o: IHubSearchOptions = {};
await _searchGroups(f, o);
expect(searchGroupsSpy.calls.count()).toBe(1, "should call searchGroups");
const [expectedParams] = searchGroupsSpy.calls.argsFor(0);
expect(expectedParams.q).toEqual("water");
expect(expectedParams.portal).toBe("https://www.arcgis.com/sharing/rest");
});

it("uses specified apis, passes num", async () => {
const searchGroupsSpy = spyOn(Portal, "searchGroups").and.callFake(() => {
return Promise.resolve(cloneObject(SimpleResponse));
});
const f: Filter<"group"> = {
filterType: "group",
term: "water",
};
const o: IHubSearchOptions = {
apis: ["arcgisQA"],
num: 6,
};
await _searchGroups(f, o);
expect(searchGroupsSpy.calls.count()).toBe(1, "should call searchGroups");
const [expectedParams] = searchGroupsSpy.calls.argsFor(0);

expect(expectedParams.q).toEqual("water");
expect(expectedParams.num).toEqual(6);
expect(expectedParams.portal).toBe("https://qaext.arcgis.com/sharing/rest");
});
it("passes auth", async () => {
const searchGroupsSpy = spyOn(Portal, "searchGroups").and.callFake(() => {
return Promise.resolve(cloneObject(SimpleResponse));
});
const f: Filter<"group"> = {
filterType: "group",
term: "water",
};
const o: IHubSearchOptions = {
authentication: MOCK_AUTH,
};
const chk = await _searchGroups(f, o);
expect(searchGroupsSpy.calls.count()).toBe(1, "should call searchGroups");
const [expectedParams] = searchGroupsSpy.calls.argsFor(0);
expect(expectedParams.authentication).toBe(MOCK_AUTH);
const g1 = chk.results[0];
expect(g1.id).toBe("7d9cc5e39a8f4c0aa29e04a473bf4703");
expect(g1.thumbnailUrl).toBeDefined();
});
});
Loading

0 comments on commit 496748b

Please sign in to comment.