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: scheduler force update #1494

Merged
merged 12 commits into from
May 6, 2024
9 changes: 9 additions & 0 deletions packages/common/src/content/_internal/ContentSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ export const ContentSchema: IConfigurationSchema = {
},
schedule: {
type: "object",
properties: {
_forceUpdate: {
type: "array",
items: {
type: "boolean",
enum: [true],
abp6318 marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
},
},
} as IConfigurationSchema;
29 changes: 21 additions & 8 deletions packages/common/src/content/_internal/ContentUiSchemaSettings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { checkPermission } from "../..";
import { IArcGISContext } from "../../ArcGISContext";
import { EntityEditorOptions } from "../../core/schemas/internal/EditorOptions";
import { IUiSchema } from "../../core/schemas/types";
import { IUiSchema, UiSchemaRuleEffects } from "../../core/schemas/types";
import { IHubEditableContent } from "../../core/types/IHubEditableContent";
import { isHostedFeatureServiceEntity } from "../hostedServiceUtils";

Expand Down Expand Up @@ -45,13 +45,26 @@ export const buildUiSchema = async (
{ type: "weekly" },
{ type: "monthly" },
{ type: "yearly" },
// uncomment this when the manual option is available
// {
// label: `option.manual.label`,
// type: "manual",
// helperActionIcon: "information-f",
// helperActionText: "option.manual.helperActionText",
// },
{
type: "manual",
helperActionIcon: "information-f",
helperActionText: `{{${i18nScope}.fields.schedule.manual.helperActionText:translate}}`,
},
],
},
},
// force update checkbox -- TODO: replace with button once available
{
type: "Control",
scope: "/properties/schedule/properties/_forceUpdate",
abp6318 marked this conversation as resolved.
Show resolved Hide resolved
options: {
control: "hub-field-input-tile-select",
type: "checkbox",
labels: [
`{{${i18nScope}.fields.schedule.forceUpdateButton.label:translate}}`,
],
descriptions: [
`{{${i18nScope}.fields.schedule.forceUpdateButton.description:translate}}`,
],
},
},
Expand Down
12 changes: 0 additions & 12 deletions packages/common/src/content/_internal/getSchedulerApiUrl.ts

This file was deleted.

35 changes: 34 additions & 1 deletion packages/common/src/content/_internal/internalContentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import { _getHubUrlFromPortalHostname } from "../../urls/_get-hub-url-from-porta
import { IRequestOptions } from "@esri/arcgis-rest-request";
import { geojsonToArcGIS } from "@terraformer/arcgis";
import { Polygon } from "geojson";
import { fetchItemEnrichments } from "../../items/_enrichments";
import { getHubApiUrl } from "../../api";
import { IUserRequestOptions } from "@esri/arcgis-rest-auth";

/**
* Hashmap of Hub environment and application url surfix
Expand Down Expand Up @@ -966,3 +967,35 @@ const getUrbanModelEditUrl = (item: IItem, requestOptions: IRequestOptions) => {
const isPortalFromUrl = (portalUrl: string): boolean => {
return portalUrl.indexOf("arcgis.com") === -1;
};

export function getSchedulerApiUrl(
itemId: string,
requestOptions: IRequestOptions
): string {
const hubApiUrlRoot = getHubApiUrlRoot(requestOptions);
return `${hubApiUrlRoot}/api/download/v1/items/${itemId}/schedule`;
}

export function getHubApiUrlRoot(requestOptions: IRequestOptions): string {
// sometimes the url has /api/v3 at the end, so we need to remove it
const hubApiUrlWithVersion = getHubApiUrl(requestOptions);
return hubApiUrlWithVersion.replace(/\/api\/v3$/, "");
}

export const forceUpdateContent = async (
itemId: string,
requestOptions: IUserRequestOptions
) => {
const hubApiUrlRoot = getHubApiUrl(requestOptions);
const url = `${hubApiUrlRoot}/api/v3/jobs/item/${itemId}/harvest`;
const options = {
method: "POST",
headers: {
"content-type": "application/json",
authorization: requestOptions.authentication.token,
},
};

const response = await fetch(url, options);
return response.ok;
};
10 changes: 10 additions & 0 deletions packages/common/src/content/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {
isDownloadSchedulingAvailable,
maybeUpdateSchedule,
} from "./manageSchedule";
import { forceUpdateContent } from "./_internal/internalContentUtils";
import { deepEqual } from "../objects";

// TODO: move this to defaults?
const DEFAULT_CONTENT_MODEL: IModel = {
Expand Down Expand Up @@ -174,6 +176,14 @@ export async function updateContent(
}

if (isDownloadSchedulingAvailable(requestOptions, content.access)) {
// if schedule has "Force Update" checked and clicked save, initiate an update
if (deepEqual((content.schedule as any)._forceUpdate, [true])) {
// [true]
await forceUpdateContent(item.id, requestOptions);
}

delete (content.schedule as any)._forceUpdate;
abp6318 marked this conversation as resolved.
Show resolved Hide resolved

await maybeUpdateSchedule(content, requestOptions);
}

Expand Down
19 changes: 16 additions & 3 deletions packages/common/src/content/manageSchedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { IHubSchedule, IHubScheduleResponse } from "../core/types/IHubSchedule";
import { cloneObject } from "../util";
import { deepEqual } from "../objects/deepEqual";
import { AccessLevel, IHubEditableContent } from "../core";
import { getSchedulerApiUrl } from "./_internal/getSchedulerApiUrl";
import { getSchedulerApiUrl } from "./_internal/internalContentUtils";

// Any code referencing these functions must first pass isDownloadSchedulingAvailable

Expand All @@ -27,10 +27,20 @@ export const getSchedule = async (
} as IHubScheduleResponse;
}

// if the schedule mode is set to manual, return it
if (schedule.mode === "manual") {
return {
schedule: {
mode: "manual",
},
message: `Download schedule found for item ${itemId}`,
statusCode: 200,
} as IHubScheduleResponse;
}

// if the schedule is set, return it with added mode
delete schedule.itemId;
switch (schedule.cadence) {
// TODO: add manual option here when option is viable
case "daily":
case "weekly":
case "monthly":
Expand Down Expand Up @@ -58,7 +68,10 @@ export const setSchedule = async (
requestOptions: IRequestOptions
): Promise<IHubScheduleResponse> => {
const body = cloneObject(schedule);
delete body.mode;
if (body.mode !== "manual") {
// remove mode if not manual
delete body.mode;
}
const url = getSchedulerApiUrl(itemId, requestOptions);
const options = {
method: "POST",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,25 @@ describe("buildUiSchema: content settings", () => {
{ type: "weekly" },
{ type: "monthly" },
{ type: "yearly" },
// uncomment this when the manual option is available
// {
// label: `option.manual.label`,
// type: "manual",
// helperActionIcon: "information-f",
// helperActionText: "option.manual.helperActionText",
// },
{
type: "manual",
helperActionIcon: "information-f",
helperActionText: `{{some.scope.fields.schedule.manual.helperActionText:translate}}`,
},
],
},
},
{
type: "Control",
scope: "/properties/schedule/properties/_forceUpdate",
options: {
control: "hub-field-input-tile-select",
type: "checkbox",
labels: [
`{{some.scope.fields.schedule.forceUpdateButton.label:translate}}`,
],
descriptions: [
`{{some.scope.fields.schedule.forceUpdateButton.description:translate}}`,
],
},
},
Expand Down
5 changes: 3 additions & 2 deletions packages/common/test/content/edit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ describe("content editing:", () => {
// Indicates that Extract should enabled on the service,
// Since it already is, nothing should change
serverExtractCapability: true,
schedule: { mode: "automatic" },
schedule: { mode: "automatic", _forceUpdate: [] } as any,
access: "public",
};
const chk = await updateContent(content, {
...MOCK_HUB_REQOPTS,
Expand Down Expand Up @@ -244,7 +245,7 @@ describe("content editing:", () => {
// Indicates that Extract should enabled on the service,
// Since it currently isn't, the service will be updated
serverExtractCapability: true,
schedule: { mode: "automatic" },
schedule: { mode: "automatic", _forceUpdate: [true] } as any,
access: "public",
};
const chk = await updateContent(content, {
Expand Down
49 changes: 38 additions & 11 deletions packages/common/test/content/manageSchedule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { MOCK_HUB_REQOPTS } from "../mocks/mock-auth";
import { IHubEditableContent } from "../../src/core/types/IHubEditableContent";
import * as fetchMock from "fetch-mock";
import { getSchedulerApiUrl } from "../../src/content/_internal/getSchedulerApiUrl";
import { getSchedulerApiUrl } from "../../src/content/_internal/internalContentUtils";

describe("manageSchedule", () => {
afterEach(() => {
Expand All @@ -34,7 +34,6 @@ describe("manageSchedule", () => {
"https://hubqa.arcgis.com/api/download/v1/items/123/schedule"
);
});

it("getSchedule: returns an error if no schedule is set", async () => {
const item = { id: "123" };
fetchMock.once(
Expand All @@ -54,15 +53,15 @@ describe("manageSchedule", () => {
);
expect(fetchMock.calls().length).toBe(1);
});

it("getSchedule: returns schedule if set", async () => {
it("getSchedule: returns schedule of mode 'scheduled' if set", async () => {
const item = { id: "123" };
fetchMock.once(
`https://hubqa.arcgis.com/api/download/v1/items/${item.id}/schedule`,
{
cadence: "daily",
hour: 0,
timezone: "America/New_York",
itemId: "123",
}
);
const response: IHubScheduleResponse = await getSchedule(
Expand All @@ -77,8 +76,25 @@ describe("manageSchedule", () => {
});
expect(fetchMock.calls().length).toBe(1);
});

it("setSchedule: sets the item's schedule", async () => {
it("getSchedule: returns schedule of mode 'manual' if set", async () => {
const item = { id: "123" };
fetchMock.once(
`https://hubqa.arcgis.com/api/download/v1/items/${item.id}/schedule`,
{
mode: "manual",
itemId: "123",
}
);
const response: IHubScheduleResponse = await getSchedule(
item.id,
MOCK_HUB_REQOPTS
);
expect(response.schedule).toEqual({
mode: "manual",
});
expect(fetchMock.calls().length).toBe(1);
});
it("setSchedule: sets the item's schedule of mode 'scheduled'", async () => {
const item = { id: "123" };
const schedule = {
mode: "scheduled",
Expand All @@ -98,7 +114,23 @@ describe("manageSchedule", () => {
expect(response.message).toEqual("Download schedule set successfully.");
expect(fetchMock.calls().length).toBe(1);
});
it("setSchedule: sets the item's schedule of mode 'manual'", async () => {
const item = { id: "123" };
const schedule = {
mode: "manual",
} as IHubSchedule;

fetchMock.post(
`https://hubqa.arcgis.com/api/download/v1/items/${item.id}/schedule`,
{
message: "Download schedule set successfully.",
}
);

const response = await setSchedule(item.id, schedule, MOCK_HUB_REQOPTS);
expect(response.message).toEqual("Download schedule set successfully.");
expect(fetchMock.calls().length).toBe(1);
});
it("setSchedule: attempts to set an invalid schedule", async () => {
const item = { id: "123" };
const schedule = {
Expand All @@ -123,7 +155,6 @@ describe("manageSchedule", () => {
);
expect(fetchMock.calls().length).toBe(1);
});

it("deleteSchedule: tries to delete an item's schedule", async () => {
const item = { id: "123" };

Expand All @@ -138,7 +169,6 @@ describe("manageSchedule", () => {
expect(response.message).toEqual("Download schedule deleted successfully.");
expect(fetchMock.calls().length).toBe(1);
});

it("maybeUpdateSchedule: no schedule is set, and updating to automatic is not needed", async () => {
const item = { id: "123" };
const content = {
Expand All @@ -161,7 +191,6 @@ describe("manageSchedule", () => {
);
expect(fetchMock.calls().length).toBe(1);
});

it("maybeUpdateSchedule: no schedule is set, and updating to scheduled is needed", async () => {
const item = { id: "123" };
const content = {
Expand Down Expand Up @@ -194,7 +223,6 @@ describe("manageSchedule", () => {
expect(response.message).toEqual("Download schedule set successfully.");
expect(fetchMock.calls().length).toBe(2);
});

it("maybeUpdateSchedule: schedule is set, and updating to automatic requires deleting the schedule", async () => {
const item = { id: "123" };
const content = {
Expand Down Expand Up @@ -222,7 +250,6 @@ describe("manageSchedule", () => {
expect(response.message).toEqual("Download schedule deleted successfully.");
expect(fetchMock.calls().length).toBe(2);
});

it("maybeUpdateSchedule: schedule is set, and no action is needed as schedules deepEqual each other", async () => {
const item = { id: "123" };
const content = {
Expand Down
Loading