diff --git a/.gitignore b/.gitignore index dc4ff6bda8..50d277ddc7 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,6 @@ packages/*/debug/ # no idea /packages/*/.rpt2_cache + +# packages in development +packages/arcgis-rest-portal/ \ No newline at end of file diff --git a/packages/arcgis-rest-common-types/src/index.ts b/packages/arcgis-rest-common-types/src/index.ts index 694541cfa2..6708c7a6ca 100644 --- a/packages/arcgis-rest-common-types/src/index.ts +++ b/packages/arcgis-rest-common-types/src/index.ts @@ -28,3 +28,33 @@ export interface IPoint { y: number; spatialReference?: ISpatialReference; } + +/** + * Params for paging operations + */ +export interface IPagingParams { + start?: number; + num?: number; +} + +/** + * Portal Item + */ +export interface IItem { + id?: string; + owner: string; + title: string; + type: string; + tags: string[]; + typeKeywords: string[]; + description?: string; + snippet?: string; + documentation?: string; + extent?: number[][]; + categories?: string[]; + spatialReference?: any; + culture?: string; + properties?: any; + url?: string; + [key: string]: any; +} diff --git a/packages/arcgis-rest-groups/package-lock.json b/packages/arcgis-rest-groups/package-lock.json new file mode 100644 index 0000000000..ffb34e1f41 --- /dev/null +++ b/packages/arcgis-rest-groups/package-lock.json @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "tslib": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.0.tgz", + "integrity": "sha512-ymKWWZJST0/CkgduC2qkzjMOWr4bouhuURNXCn/inEX0L57BnRG6FhX76o7FOnsjHazCjfU2LKeSrlS2sIKQJg==" + } + } +} diff --git a/packages/arcgis-rest-groups/package.json b/packages/arcgis-rest-groups/package.json new file mode 100644 index 0000000000..4f920851a4 --- /dev/null +++ b/packages/arcgis-rest-groups/package.json @@ -0,0 +1,46 @@ +{ + "name": "@esri/arcgis-rest-groups", + "version": "1.0.0", + "description": "Portal Group helpers for @esri/arcgis-rest-request", + "main": "dist/node/index.js", + "browser": "dist/browser/arcgis-rest-groups.umd.js", + "module": "dist/esm/index.js", + "js:next": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^1.7.1" + }, + "peerDependencies": { + "@esri/arcgis-rest-request": "^1.0.0", + "@esri/arcgis-rest-auth": "^1.0.0", + "@esri/arcgis-rest-common-types": "^1.0.0" + }, + "devDependencies": { + "@esri/arcgis-rest-request": "^1.0.0", + "@esri/arcgis-rest-auth": "^1.0.0", + "@esri/arcgis-rest-common-types": "^1.0.0" + }, + "scripts": { + "prepublish": "npm run build", + "build": "npm run build:node && npm run build:umd && npm run build:esm", + "build:esm": "tsc -p ./tsconfig.json --module es2015 --outDir ./dist/esm --declaration", + "build:umd": "rollup -c ../../rollup.config.umd.js", + "build:node": "tsc -p ./tsconfig.json --module commonjs --outDir ./dist/node" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Esri/arcgis-rest-js.git" + }, + "contributors": [ + { + "name": "Dave Bouwman", + "email": "dbouwman@esri.com", + "url": "http://blog.davebouwman.com/" + } + ], + "bugs": { + "url": "https://github.com/Esri/arcgis-rest-js/issues" + }, + "homepage": "https://github.com/Esri/arcgis-rest-js#readme" +} diff --git a/packages/arcgis-rest-groups/src/groups.ts b/packages/arcgis-rest-groups/src/groups.ts new file mode 100644 index 0000000000..a9cca4b578 --- /dev/null +++ b/packages/arcgis-rest-groups/src/groups.ts @@ -0,0 +1,268 @@ +/* Copyright (c) 2017 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ +import { + request, + IRequestOptions, + IParams, + getPortalUrl +} from "@esri/arcgis-rest-request"; + +import { IPagingParams, IItem } from "@esri/arcgis-rest-common-types"; + +export interface IGroup { + id?: string; + owner: string; + title: string; + tags: string[]; + description?: string; + categories?: string[]; + culture?: string; + [key: string]: any; +} + +export interface IGroupSearchRequest extends IPagingParams { + q: string; + sortField?: string; + sortOrder?: string; + [key: string]: any; +} + +export interface IGroupSearchResult { + query: string; // matches the api's form param + total: number; + start: number; + num: number; + nextStart: number; + results: IGroup[]; +} + +export interface IGroupContentResult { + total: number; + start: number; + num: number; + nextStart: number; + items: IItem[]; +} + +export interface IGroupUsersResult { + owner: string; + admins: string[]; + users: string[]; +} + +/** + * Search for groups via the portal api + * + * ```js + * import { searchGroups } from '@esri/arcgis-rest-groups'; + * + * searchgroups({q:'water'}) + * .then((results) => { + * console.log(response.results.total); // 355 + * }) + * ``` + * + * @param searchForm - Search request + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with the data from the response. + */ +export function searchGroups( + searchForm: IGroupSearchRequest, + requestOptions?: IRequestOptions +): Promise { + // construct the search url + const url = `${getPortalUrl(requestOptions)}/community/groups`; + + // default to a GET request + const options: IRequestOptions = { + ...{ httpMethod: "GET" }, + ...requestOptions + }; + + // send the request + return request(url, searchForm, options); +} + +/** + * + * @param id - Group Id + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with the data from the response. + */ +export function getGroup( + id: string, + requestOptions?: IRequestOptions +): Promise { + const url = `${getPortalUrl(requestOptions)}/community/groups/${id}`; + // default to a GET request + const options: IRequestOptions = { + ...{ httpMethod: "GET" }, + ...requestOptions + }; + return request(url, null, options); +} + +/** + * Returns the content of a Group. Since the group may contain 1000s of items + * the requestParams allow for paging. + * @param id - Group Id + * @param requestParams - Paging parameters + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with the content of the group. + */ +export function getGroupContent( + id: string, + requestParams?: IPagingParams, + requestOptions?: IRequestOptions +): Promise { + const url = `${getPortalUrl(requestOptions)}/content/groups/${id}`; + const params: IPagingParams = { + ...{ start: 1, num: 100 }, + ...requestParams + }; + // default to a GET request + const options: IRequestOptions = { + ...{ httpMethod: "GET" }, + ...requestOptions + }; + return request(url, params, options); +} + +/** + * Get the usernames of the admins and members. Does not return actual 'User' objects. Those must be + * retrieved via separate calls to the User's API. + * @param id - Group Id + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with arrays of the group admin usernames and the member usernames + */ +export function getGroupUsers( + id: string, + requestOptions?: IRequestOptions +): Promise { + const url = `${getPortalUrl(requestOptions)}/community/groups/${id}/users`; + // default to a GET request + const options: IRequestOptions = { + ...{ httpMethod: "GET" }, + ...requestOptions + }; + return request(url, null, options); +} + +/** + * Serialize an group into a json format accepted by the Portal API + * for create and update operations + * + * @param item IGroup to be serialized + * @returns a formatted json object to be sent to Portal + */ +function serializeGroup(group: IGroup): any { + // create a clone so we're not messing with the original + const clone = JSON.parse(JSON.stringify(group)); + // join and tags... + clone.tags = clone.tags.join(", "); + return clone; +} + +/** + * Create a new Group. + * Note: The name must be unique within the user's organization. + * @param group - Group to create + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with the success/failure status of the request + */ +export function createGroup( + group: IGroup, + requestOptions: IRequestOptions +): Promise { + const url = `${getPortalUrl(requestOptions)}/community/createGroup`; + // default to a POST request + const options: IRequestOptions = { + ...{ httpMethod: "POST" }, + ...requestOptions + }; + // serialize the group into something Portal will accept + const requestParams = serializeGroup(group); + return request(url, requestParams, options); +} + +/** + * Update the properties of a group - title, tags etc. + * @param group - Group to update + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with the success/failure status of the request + */ +export function updateGroup( + group: IGroup, + requestOptions: IRequestOptions +): Promise { + const url = `${getPortalUrl( + requestOptions + )}/community/groups/${group.id}/update`; + // default to a POST request + const options: IRequestOptions = { + ...{ httpMethod: "POST" }, + ...requestOptions + }; + // serialize the group into something Portal will accept + const requestParams = serializeGroup(group); + return request(url, requestParams, options); +} + +/** + * Delete a group. + * @param id - Group Id + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with the success/failure status of the request + */ +export function removeGroup( + id: string, + requestOptions: IRequestOptions +): Promise { + const url = `${getPortalUrl(requestOptions)}/community/groups/${id}/delete`; + // default to a POST request + const options: IRequestOptions = { + ...{ httpMethod: "POST" }, + ...requestOptions + }; + return request(url, null, options); +} + +/** + * Protect a Group. This simply means a user must unprotect the group prior to deleting it + * @param id - Group Id + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with the success/failure status of the request + */ +export function protectGroup( + id: string, + requestOptions: IRequestOptions +): Promise { + const url = `${getPortalUrl(requestOptions)}/community/groups/${id}/protect`; + // default to a POST request + const options: IRequestOptions = { + ...{ httpMethod: "POST" }, + ...requestOptions + }; + return request(url, null, options); +} + +/** + * Unprotect a Group. + * @param id - Group Id + * @param requestOptions - Options for the request + * @returns A Promise that will resolve with the success/failure status of the request + */ +export function unprotectGroup( + id: string, + requestOptions: IRequestOptions +): Promise { + const url = `${getPortalUrl( + requestOptions + )}/community/groups/${id}/unprotect`; + // default to a POST request + const options: IRequestOptions = { + ...{ httpMethod: "POST" }, + ...requestOptions + }; + return request(url, null, options); +} diff --git a/packages/arcgis-rest-groups/src/index.ts b/packages/arcgis-rest-groups/src/index.ts new file mode 100644 index 0000000000..ba31723b00 --- /dev/null +++ b/packages/arcgis-rest-groups/src/index.ts @@ -0,0 +1 @@ +export * from "./groups"; diff --git a/packages/arcgis-rest-groups/test/groups.test.ts b/packages/arcgis-rest-groups/test/groups.test.ts new file mode 100644 index 0000000000..9b8b792391 --- /dev/null +++ b/packages/arcgis-rest-groups/test/groups.test.ts @@ -0,0 +1,290 @@ +import { + searchGroups, + getGroup, + getGroupContent, + getGroupUsers, + createGroup, + updateGroup, + removeGroup, + protectGroup, + unprotectGroup +} from "../src/index"; + +import { + GroupSearchResponse, + GroupEditResponse, + GroupResponse, + GroupContentResponse, + GroupUsersResponse +} from "./mocks/responses"; + +import * as fetchMock from "fetch-mock"; + +describe("groups", () => { + let paramsSpy: jasmine.Spy; + + beforeEach(() => { + paramsSpy = spyOn(FormData.prototype, "append").and.callThrough(); + }); + + afterAll(() => { + paramsSpy.calls.reset(); + }); + + afterEach(fetchMock.restore); + + describe("searchGroups", () => { + it("should make a simple, unauthenticated group search request", done => { + fetchMock.once("*", GroupSearchResponse); + + searchGroups({ q: "water" }) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://www.arcgis.com/sharing/rest/community/groups?f=json&q=water" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should take num, start, sortField, sortOrder and construct the request", done => { + fetchMock.once("*", GroupSearchResponse); + searchGroups({ + q: "water", + start: 4, + num: 7, + sortField: "owner", + sortOrder: "desc" + }) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://www.arcgis.com/sharing/rest/community/groups?f=json&q=water&start=4&num=7&sortField=owner&sortOrder=desc" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); + describe("getGroup", () => { + it("should return a group", done => { + fetchMock.once("*", GroupResponse); + getGroup("3ef") + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://www.arcgis.com/sharing/rest/community/groups/3ef?f=json" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); + describe("getGroupContent", () => { + it("should return group content", done => { + fetchMock.once("*", GroupContentResponse); + getGroupContent("3ef") + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://www.arcgis.com/sharing/rest/content/groups/3ef?f=json&start=1&num=100" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should return group content, paged", done => { + fetchMock.once("*", GroupContentResponse); + getGroupContent("3ef", { start: 4, num: 7 }) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://www.arcgis.com/sharing/rest/content/groups/3ef?f=json&start=4&num=7" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); + + describe("authenticted methods", () => { + const MOCK_AUTH = { + getToken() { + return Promise.resolve("fake-token"); + }, + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }; + const MOCK_REQOPTS = { + authentication: MOCK_AUTH + }; + + it("should make a simple, authenticated group search request", done => { + fetchMock.once("*", GroupSearchResponse); + + searchGroups({ q: "water" }, MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups?f=json&q=water&token=fake-token" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should create a group", done => { + fetchMock.once("*", GroupEditResponse); + const fakeGroup = { + title: "fake group", + owner: "fakeUser", + tags: ["foo", "bar"], + description: "my fake group" + }; + createGroup(fakeGroup, MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/createGroup" + ); + expect(options.method).toBe("POST"); + expect(paramsSpy).toHaveBeenCalledWith("f", "json"); + expect(paramsSpy).toHaveBeenCalledWith("token", "fake-token"); + expect(paramsSpy).toHaveBeenCalledWith("owner", "fakeUser"); + // ensure the array props are serialized into strings + expect(paramsSpy).toHaveBeenCalledWith("tags", "foo, bar"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should update a group", done => { + fetchMock.once("*", GroupEditResponse); + const fakeGroup = { + id: "5bc", + title: "fake group", + owner: "fakeUser", + tags: ["foo", "bar"], + description: "my fake group" + }; + updateGroup(fakeGroup, MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/5bc/update" + ); + expect(options.method).toBe("POST"); + expect(paramsSpy).toHaveBeenCalledWith("f", "json"); + expect(paramsSpy).toHaveBeenCalledWith("token", "fake-token"); + expect(paramsSpy).toHaveBeenCalledWith("owner", "fakeUser"); + // ensure the array props are serialized into strings + expect(paramsSpy).toHaveBeenCalledWith("tags", "foo, bar"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should remove a group", done => { + fetchMock.once("*", GroupEditResponse); + + removeGroup("5bc", MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/5bc/delete" + ); + expect(options.method).toBe("POST"); + expect(paramsSpy).toHaveBeenCalledWith("f", "json"); + expect(paramsSpy).toHaveBeenCalledWith("token", "fake-token"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should protect a group", done => { + fetchMock.once("*", GroupEditResponse); + + protectGroup("5bc", MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/5bc/protect" + ); + expect(options.method).toBe("POST"); + expect(paramsSpy).toHaveBeenCalledWith("f", "json"); + expect(paramsSpy).toHaveBeenCalledWith("token", "fake-token"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should unprotect a group", done => { + fetchMock.once("*", GroupEditResponse); + + unprotectGroup("5bc", MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/5bc/unprotect" + ); + expect(options.method).toBe("POST"); + expect(paramsSpy).toHaveBeenCalledWith("f", "json"); + expect(paramsSpy).toHaveBeenCalledWith("token", "fake-token"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should return group users", done => { + fetchMock.once("*", GroupUsersResponse); + + getGroupUsers("5bc", MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/5bc/users?f=json&token=fake-token" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + // it('should make authenticated call to get the group content', done => {}) + }); +}); diff --git a/packages/arcgis-rest-groups/test/mocks/responses.ts b/packages/arcgis-rest-groups/test/mocks/responses.ts new file mode 100644 index 0000000000..6db84422e1 --- /dev/null +++ b/packages/arcgis-rest-groups/test/mocks/responses.ts @@ -0,0 +1,135 @@ +import { + IGroup, + IGroupSearchResult, + IGroupContentResult, + IGroupUsersResult +} from "../../src/groups"; + +export const GroupSearchResponse: IGroupSearchResult = { + query: "* AND owner:dcadmin", + total: 358, + start: 1, + num: 1, + nextStart: 2, + results: [ + { + id: "ef45564655144f15b5903d05a24493d9", + title: "0 alex test", + isInvitationOnly: false, + owner: "dcadmin", + description: null, + snippet: "alex test", + tags: ["7320", "test", "alex"], + phone: null, + sortField: "title", + sortOrder: "asc", + isViewOnly: false, + isFav: false, + isOpenData: true, + thumbnail: null, + created: 1427987785000, + modified: 1490731120000, + provider: null, + providerGroupName: null, + isReadOnly: false, + access: "public", + capabilities: [], + protected: false, + autoJoin: false, + notificationsEnabled: false + } + ] +}; + +export const GroupEditResponse: any = { + success: true, + id: "3efakegroupid" +}; + +export const GroupUsersResponse: IGroupUsersResult = { + owner: "vader", + admins: ["vader", "tarkin"], + users: ["luke", "leia"] +}; + +export const GroupResponse: IGroup = { + id: "ef45564655144f15b5903d05a24493d9", + title: "0 alex test", + isInvitationOnly: false, + owner: "dcadmin", + description: "", + snippet: "alex test", + tags: ["7320", "test", "alex"], + phone: "", + sortField: "title", + sortOrder: "asc", + isViewOnly: false, + isFav: false, + isOpenData: true, + thumbnail: null, + created: 1427987785000, + modified: 1490731120000, + provider: null, + providerGroupName: null, + isReadOnly: false, + access: "public", + capabilities: [], + userMembership: { + username: "dcadmin", + memberType: "owner", + applications: 0 + }, + collaborationInfo: {}, + protected: false, + autoJoin: false, + notificationsEnabled: false +}; + +export const GroupContentResponse: IGroupContentResult = { + total: 36, + start: 1, + num: 1, + nextStart: 2, + items: [ + { + id: "59dd250ab1a54660b11f4f3434a3e481", + owner: "dcadmin", + created: 1410610860000, + modified: 1498835611000, + guid: null, + name: "LivingAtlasThemes.pdf", + title: "LivingAtlasThemes", + type: "PDF", + typeKeywords: ["Data", "Document", "PDF"], + description: null, + tags: ["doc"], + snippet: null, + thumbnail: "thumbnail/ago_downloaded.png", + documentation: null, + extent: [], + categories: [], + spatialReference: null, + accessInformation: null, + licenseInfo: "CC0", + culture: "en-us", + properties: null, + url: null, + proxyFilter: null, + access: "public", + size: 75476, + appCategories: [], + industries: [], + languages: [], + largeThumbnail: null, + banner: null, + screenshots: [], + listed: false, + numComments: 0, + numRatings: 0, + avgRating: 0, + numViews: 1301, + groupCategories: [], + scoreCompleteness: 50 + } + ] +}; diff --git a/packages/arcgis-rest-groups/tsconfig.json b/packages/arcgis-rest-groups/tsconfig.json new file mode 100644 index 0000000000..400168ea91 --- /dev/null +++ b/packages/arcgis-rest-groups/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/arcgis-rest-items/package-lock.json b/packages/arcgis-rest-items/package-lock.json new file mode 100644 index 0000000000..ffb34e1f41 --- /dev/null +++ b/packages/arcgis-rest-items/package-lock.json @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "tslib": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.0.tgz", + "integrity": "sha512-ymKWWZJST0/CkgduC2qkzjMOWr4bouhuURNXCn/inEX0L57BnRG6FhX76o7FOnsjHazCjfU2LKeSrlS2sIKQJg==" + } + } +} diff --git a/packages/arcgis-rest-items/src/items.ts b/packages/arcgis-rest-items/src/items.ts index d61394a491..fc9df8d5ab 100644 --- a/packages/arcgis-rest-items/src/items.ts +++ b/packages/arcgis-rest-items/src/items.ts @@ -7,26 +7,7 @@ import { getPortalUrl } from "@esri/arcgis-rest-request"; -import { IExtent } from "@esri/arcgis-rest-common-types"; - -export interface IItem { - id?: string; - owner: string; - title: string; - type: string; - tags: string[]; - typeKeywords: string[]; - description?: string; - snippet?: string; - documentation?: string; - extent?: number[][]; - categories?: string[]; - spatialReference?: any; - culture?: string; - properties?: any; - url?: string; - [key: string]: any; -} +import { IExtent, IItem } from "@esri/arcgis-rest-common-types"; export interface ISearchRequest { q: string; diff --git a/packages/arcgis-rest-items/test/mocks/responses.ts b/packages/arcgis-rest-items/test/mocks/responses.ts index e4962a48e4..f0cf4c4a04 100644 --- a/packages/arcgis-rest-items/test/mocks/responses.ts +++ b/packages/arcgis-rest-items/test/mocks/responses.ts @@ -1,4 +1,6 @@ -import { ISearchResult, IItem } from "../../src/items"; +import { ISearchResult } from "../../src/items"; +import { IItem } from "@esri/arcgis-rest-common-types"; + export const SearchResponse: ISearchResult = { query: "", total: 10795, diff --git a/packages/arcgis-rest-request/src/index.ts b/packages/arcgis-rest-request/src/index.ts index 2a10c3c821..851435970e 100644 --- a/packages/arcgis-rest-request/src/index.ts +++ b/packages/arcgis-rest-request/src/index.ts @@ -6,4 +6,5 @@ export * from "./utils/ArcGISRequestError"; export * from "./utils/ArcGISAuthError"; export * from "./utils/ErrorTypes"; export * from "./utils/process-params"; +export * from "./utils/get-portal"; export * from "./utils/get-portal-url"; diff --git a/packages/arcgis-rest-request/src/utils/get-portal.ts b/packages/arcgis-rest-request/src/utils/get-portal.ts new file mode 100644 index 0000000000..8663572737 --- /dev/null +++ b/packages/arcgis-rest-request/src/utils/get-portal.ts @@ -0,0 +1,45 @@ +/* Copyright (c) 2017 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { request, IRequestOptions, IParams } from "../request"; + +import { getPortalUrl } from "./get-portal-url"; + +export interface IPortal { + id: string; + isPortal: boolean; + name: string; + [key: string]: any; +} + +/** + * Get the portal + * @param requestOptions + */ +export function getSelf(requestOptions?: IRequestOptions): Promise { + // just delegate to getPortal w/o an id + return getPortal(null, requestOptions); +} + +/** + * Get a portal by id. If no id is passed, it will call portals/self + * @param id + * @param requestOptions + */ +export function getPortal( + id?: string, + requestOptions?: IRequestOptions +): Promise { + // construct the search url + const idOrSelf = id ? id : "self"; + const url = `${getPortalUrl(requestOptions)}/portals/${idOrSelf}`; + + // default to a GET request + const options: IRequestOptions = { + ...{ httpMethod: "GET" }, + ...requestOptions + }; + + // send the request + return request(url, null, options); +} diff --git a/packages/arcgis-rest-request/test/mocks/portal.ts b/packages/arcgis-rest-request/test/mocks/portal.ts new file mode 100644 index 0000000000..22c050033d --- /dev/null +++ b/packages/arcgis-rest-request/test/mocks/portal.ts @@ -0,0 +1,109 @@ +import { IPortal } from "../../src/utils/get-portal"; + +export const PortalResponse: IPortal = { + access: "public", + allSSL: false, + allowedRedirectUris: [], + analysisLayersGroupQuery: + 'title:"Living Atlas Analysis Layers" AND owner:esri', + authorizedCrossOriginDomains: [], + availableCredits: 9584.193, + basemapGalleryGroupQuery: "id:06d589ea1fdb47768ee3870f497333c0", + canListApps: false, + canListData: false, + canListPreProvisionedItems: false, + canProvisionDirectPurchase: false, + canSearchPublic: true, + canShareBingPublic: false, + canSharePublic: true, + canSignInArcGIS: true, + canSignInIDP: true, + colorSetsGroupQuery: 'title:"Esri Colors" AND owner:esri_en', + commentsEnabled: true, + created: 1392336986000, + creditAssignments: "enabled", + culture: "en", + customBaseUrl: "mapsdevext.arcgis.com", + databaseQuota: -1, + databaseUsage: -1, + defaultBasemap: {}, + defaultExtent: { + type: "extent", + xmin: -8589300.590116454, + ymin: 4692777.971239516, + xmax: -8562027.314872108, + ymax: 4722244.554454538, + spatialReference: { + wkid: 102100 + } + }, + defaultUserCreditAssignment: 100, + defaultVectorBasemap: { + baseMapLayers: [], + title: "Topographic" + }, + description: "We're the DC R&D Center", + eueiEnabled: false, + featuredGroups: [], + featuredGroupsId: "", + featuredItemsGroupQuery: "", + galleryTemplatesGroupQuery: 'title:"Gallery Templates" AND owner:esri_en', + hasCategorySchema: false, + helpBase: "http://docdev.arcgis.com/en/arcgis-online/", + helpMap: {}, + helperServices: {}, + homePageFeaturedContent: "", + homePageFeaturedContentCount: 12, + id: "LjjARY1mkhxulWPq", + isPortal: false, + layerTemplatesGroupQuery: 'title:"Esri Layer Templates" AND owner:esri_en', + livingAtlasGroupQuery: 'title:"Featured Maps And Apps" AND owner:esri', + maxTokenExpirationMinutes: -1, + metadataEditable: true, + metadataFormats: ["iso19139"], + modified: 1511894763000, + name: "Washington, DC", + notificationsEnabled: false, + portalHostname: "devext.arcgis.com", + portalMode: "multitenant", + portalName: "ArcGIS Online", + portalProperties: {}, + portalThumbnail: null, + region: "WO", + rotatorPanels: [ + { + id: "banner-2", + innerHTML: + "
Washington, DCWashington, DC
" + } + ], + showHomePageDescription: false, + staticImagesUrl: "http://static.arcgis.com/images", + storageQuota: 2199023255552, + storageUsage: 17622609523, + stylesGroupQuery: 'title:"Esri Styles" AND owner:esri_en', + supportsHostedServices: true, + symbolSetsGroupQuery: 'title:"Esri Symbols" AND owner:esri_en', + templatesGroupQuery: "id:f233da8d297e4ff9ae992c21f8f4010f", + thumbnail: "thumbnail.png", + units: "english", + updateUserProfileDisabled: false, + urlKey: "dc", + useStandardizedQuery: true, + useVectorBasemaps: false, + vectorBasemapGalleryGroupQuery: + 'title:"ArcGIS Online Vector Basemaps" AND owner:esri_en', + subscriptionInfo: {}, + isHubEnabled: false, + ipCntryCode: "US", + httpPort: 80, + httpsPort: 443, + supportsOAuth: true, + currentVersion: "5.4", + allowedOrigins: [], + mfaAdmins: [], + contacts: ["dcadmin"], + mfaEnabled: false, + user: {}, + appInfo: {} +}; diff --git a/packages/arcgis-rest-request/test/ArcGISAuthError.test.ts b/packages/arcgis-rest-request/test/utils/ArcGISAuthError.test.ts similarity index 98% rename from packages/arcgis-rest-request/test/ArcGISAuthError.test.ts rename to packages/arcgis-rest-request/test/utils/ArcGISAuthError.test.ts index 1e31f68b89..243fe7ba37 100644 --- a/packages/arcgis-rest-request/test/ArcGISAuthError.test.ts +++ b/packages/arcgis-rest-request/test/utils/ArcGISAuthError.test.ts @@ -1,5 +1,5 @@ -import { ArcGISAuthError, IRetryAuthError, ErrorTypes } from "../src/index"; -import { ArcGISOnlineAuthError, ArcGISOnlineError } from "./mocks/errors"; +import { ArcGISAuthError, IRetryAuthError, ErrorTypes } from "../../src/index"; +import { ArcGISOnlineAuthError, ArcGISOnlineError } from "./../mocks/errors"; import * as fetchMock from "fetch-mock"; describe("ArcGISRequestError", () => { diff --git a/packages/arcgis-rest-request/test/ArcGISRequestError.test.ts b/packages/arcgis-rest-request/test/utils/ArcGISRequestError.test.ts similarity index 91% rename from packages/arcgis-rest-request/test/ArcGISRequestError.test.ts rename to packages/arcgis-rest-request/test/utils/ArcGISRequestError.test.ts index d00f5ff32f..e13f5b61bf 100644 --- a/packages/arcgis-rest-request/test/ArcGISRequestError.test.ts +++ b/packages/arcgis-rest-request/test/utils/ArcGISRequestError.test.ts @@ -1,5 +1,5 @@ -import { ArcGISRequestError } from "../src/index"; -import { ArcGISOnlineError } from "./mocks/errors"; +import { ArcGISRequestError } from "../../src/index"; +import { ArcGISOnlineError } from "./../mocks/errors"; describe("ArcGISRequestError", () => { it("should be an instanceof Error", () => { diff --git a/packages/arcgis-rest-request/test/check-for-errors.test.ts b/packages/arcgis-rest-request/test/utils/check-for-errors.test.ts similarity index 95% rename from packages/arcgis-rest-request/test/check-for-errors.test.ts rename to packages/arcgis-rest-request/test/utils/check-for-errors.test.ts index 3b25e7d35c..9d6da9cfcf 100644 --- a/packages/arcgis-rest-request/test/check-for-errors.test.ts +++ b/packages/arcgis-rest-request/test/utils/check-for-errors.test.ts @@ -2,8 +2,8 @@ import { checkForErrors, ArcGISRequestError, ArcGISAuthError -} from "../src/index"; -import { SharingRestInfo } from "./mocks/sharing-rest-info"; +} from "../../src/index"; +import { SharingRestInfo } from "./../mocks/sharing-rest-info"; import { ArcGISOnlineError, BillingError, @@ -13,7 +13,7 @@ import { ArcGISOnlineErrorNoCode, ArcGISServerTokenRequired, ArcGISOnlineAuthError -} from "./mocks/errors"; +} from "./../mocks/errors"; describe("checkForErrors", () => { it("should pass if an error is not found", () => { diff --git a/packages/arcgis-rest-request/test/utils/portal.test.ts b/packages/arcgis-rest-request/test/utils/portal.test.ts new file mode 100644 index 0000000000..1a7b967510 --- /dev/null +++ b/packages/arcgis-rest-request/test/utils/portal.test.ts @@ -0,0 +1,94 @@ +import { getSelf, getPortal } from "../../src/index"; + +import { PortalResponse } from "./../mocks/portal"; + +import * as fetchMock from "fetch-mock"; + +describe("portal", () => { + let paramsSpy: jasmine.Spy; + + beforeEach(() => { + paramsSpy = spyOn(FormData.prototype, "append").and.callThrough(); + }); + + afterAll(() => { + paramsSpy.calls.reset(); + }); + + afterEach(fetchMock.restore); + + describe("getPortal", () => { + // setup an authmgr to use in all these tests + const MOCK_AUTH = { + getToken() { + return Promise.resolve("fake-token"); + }, + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }; + const MOCK_REQOPTS = { + authentication: MOCK_AUTH + }; + + it("should get the portal by id", done => { + fetchMock.once("*", PortalResponse); + getPortal("5BZFaKe", MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/portals/5BZFaKe?f=json&token=fake-token" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should get the portal self if no id", done => { + fetchMock.once("*", PortalResponse); + getPortal(null, MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/portals/self?f=json&token=fake-token" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); + describe("getSelf", () => { + // setup an authmgr to use in all these tests + const MOCK_AUTH = { + getToken() { + return Promise.resolve("fake-token"); + }, + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }; + const MOCK_REQOPTS = { + authentication: MOCK_AUTH + }; + + it("should get the portal by id", done => { + fetchMock.once("*", PortalResponse); + getSelf(MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/portals/self?f=json&token=fake-token" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); +}); diff --git a/packages/arcgis-rest-request/test/process-params.test.ts b/packages/arcgis-rest-request/test/utils/process-params.test.ts similarity index 97% rename from packages/arcgis-rest-request/test/process-params.test.ts rename to packages/arcgis-rest-request/test/utils/process-params.test.ts index 9180417eba..d25a6aea85 100644 --- a/packages/arcgis-rest-request/test/process-params.test.ts +++ b/packages/arcgis-rest-request/test/utils/process-params.test.ts @@ -1,4 +1,4 @@ -import { processParams } from "../src/index"; +import { processParams } from "../../src/index"; describe("processParams", () => { it("should pass non Date, Function, Array and Object params through", () => {