From 6cd40355b5eeed938d8e4dc97cfd8f6b0a11e108 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 08:21:18 +1000 Subject: [PATCH 01/30] renaming old order concept to dataset --- src/archive/index.ts | 2 +- src/orders/{order.ts => dataset.ts} | 48 ++++++----------------------- src/orders/index.ts | 12 ++++---- src/orders/status.ts | 31 +++++++++++++++++++ src/tasking/index.ts | 2 +- 5 files changed, 48 insertions(+), 47 deletions(-) rename src/orders/{order.ts => dataset.ts} (83%) create mode 100644 src/orders/status.ts diff --git a/src/archive/index.ts b/src/archive/index.ts index 91ef513..a4e0046 100644 --- a/src/archive/index.ts +++ b/src/archive/index.ts @@ -1,6 +1,6 @@ import SearchRequest from "./search-request"; import SearchResponse, { decodeResponse, decodeResultSet } from "./search/response"; -import Order, { fromJSON as OrderFromJSON } from "../orders/order"; +import Order, { fromJSON as OrderFromJSON } from "../orders/dataset"; import OrderRequest from "../orders/order-request"; import paths from "../util/paths"; import { jsonOrError, requestBuilder } from "../util/request"; diff --git a/src/orders/order.ts b/src/orders/dataset.ts similarity index 83% rename from src/orders/order.ts rename to src/orders/dataset.ts index c768ae8..0ff0a5e 100644 --- a/src/orders/order.ts +++ b/src/orders/dataset.ts @@ -1,6 +1,7 @@ import Resource, {fromJSON as resourceFromJSON} from "./resource"; import paths from "../util/paths"; import { jsonOrError, requestBuilder } from "../util/request"; +import { isStatusCode, StatusCode } from "./status"; /** * Utility class to construct Orders from JSON data @@ -9,7 +10,7 @@ import { jsonOrError, requestBuilder } from "../util/request"; * @param {requestBuilder} client the dialer * @param json the JSON content to attempt to parse into an Order */ -export function fromJSON(client: requestBuilder, json: string|{[key: string]: unknown}): Order|string { +export function fromJSON(client: requestBuilder, json: string|{[key: string]: unknown}): Dataset|string { if (typeof json === "string") { json = JSON.parse(json); } @@ -45,7 +46,7 @@ export function fromJSON(client: requestBuilder, json: string|{[key: string]: un if (typeof json.status !== "string") { return "Order status missing"; } - if (!isOrderStatus(json.status)) { + if (!isStatusCode(json.status)) { return `Order status '${json.status}' not recognized`; } if (typeof json.total !== "number") { @@ -70,7 +71,7 @@ export function fromJSON(client: requestBuilder, json: string|{[key: string]: un } } - return new Order(client, json.id, new Date(json.createdAt), new Date(json.updatedAt), json.supplier, orderKey, json.sceneID, json.status, json.total, json.type, resources, json.expiration?new Date(json.expiration as string|Date):undefined); + return new Dataset(client, json.id, new Date(json.createdAt), new Date(json.updatedAt), json.supplier, orderKey, json.sceneID, json.status, json.total, json.type, resources, json.expiration?new Date(json.expiration as string|Date):undefined); } /** @@ -80,7 +81,7 @@ export function fromJSON(client: requestBuilder, json: string|{[key: string]: un * * @see {https://arlula.com/documentation/#ref-order|Order structure reference} */ -export default class Order { +export default class Dataset { private _client: requestBuilder; private _id: string; private _createdAt: Date; @@ -88,7 +89,7 @@ export default class Order { private _supplier: string; private _orderingID: string; private _sceneID: string; - private _status: OrderStatus; + private _status: StatusCode; private _total: number; private _type: string; private _expiration?: Date; @@ -111,7 +112,7 @@ export default class Order { * @param {Resource[]} resources List of resource for this supplier (if available) * @param {Date} [exp] Expiration date for this orders resources */ - constructor(client: requestBuilder, id: string, created: Date, updated: Date, supplier: string, orderKey: string, scene: string, status: OrderStatus, total: number, type: string, resources: Resource[], exp?: Date) { + constructor(client: requestBuilder, id: string, created: Date, updated: Date, supplier: string, orderKey: string, scene: string, status: StatusCode, total: number, type: string, resources: Resource[], exp?: Date) { this._client = client; this._id = id; this._createdAt = created; @@ -151,7 +152,7 @@ export default class Order { public get sceneID(): string { return this._sceneID; } - public get status(): OrderStatus { + public get status(): StatusCode { return this._status; } public get total(): number { @@ -179,7 +180,7 @@ export default class Order { */ loadResources(): Promise { // incomplete orders don't have resources yet - if (this.status !== OrderStatus.Complete) { + if (this.status !== StatusCode.Complete) { return Promise.resolve(this._resources); } @@ -228,34 +229,3 @@ export default class Order { } } - -/** - * OrderStatus enumerates the statuses that orders may be in - * - * New ------------> "created", - * Pending --------> "pending", - * Processing -----> "processing", - * Manual ---------> "manual", - * PostProcessing -> "post-processing", - * Complete -------> "complete", - */ -export enum OrderStatus { - // common + archive orders - New = "created", - Pending = "pending", - Processing = "processing", - PostProcessing = "post-processing", - Complete = "complete", - // custom orders - Manual = "manual", - // tasking orders - PendingApproval = "pending-approval", - Rejected = "rejected", - Failed = "failed", - Rescheduled = "rescheduled", - Cancelled = "cancelled" -} - -function isOrderStatus(token: string): token is OrderStatus { - return Object.values(OrderStatus).includes(token as OrderStatus); -} diff --git a/src/orders/index.ts b/src/orders/index.ts index 21a02e3..24ee8f8 100644 --- a/src/orders/index.ts +++ b/src/orders/index.ts @@ -1,5 +1,5 @@ import { WriteStream } from "fs"; -import Order, { fromJSON } from "./order"; +import Dataset, { fromJSON } from "./dataset"; import { downloadHelper as resourceDownloader, downloadFileHelper } from "./resource"; import paths from "../util/paths"; import { jsonOrError, requestBuilder } from "../util/request"; @@ -28,7 +28,7 @@ export default class Orders { * or * @see {https://arlula.com/documentation/#ref-order|Order structure reference} */ - list(): Promise { + list(): Promise { return this._client("GET", paths.OrderList) .then(jsonOrError) .then((resp) => { @@ -36,10 +36,10 @@ export default class Orders { return Promise.reject("Orders list response is not array"); } - const orders: Order[] = []; + const orders: Dataset[] = []; for (let i=0; i { + get(id: string): Promise { return this._client("GET", paths.OrderGet+"?id="+id) .then(jsonOrError) .then((resp) => { @@ -67,7 +67,7 @@ export default class Orders { } const ord = fromJSON(this._client, resp as {[key: string]: unknown}); - if (!(ord instanceof Order)) { + if (!(ord instanceof Dataset)) { return Promise.reject(ord); } return ord; diff --git a/src/orders/status.ts b/src/orders/status.ts new file mode 100644 index 0000000..d51d378 --- /dev/null +++ b/src/orders/status.ts @@ -0,0 +1,31 @@ + +/** + * OrderStatus enumerates the statuses that orders may be in + * + * New ------------> "created", + * Pending --------> "pending", + * Processing -----> "processing", + * Manual ---------> "manual", + * PostProcessing -> "post-processing", + * Complete -------> "complete", + */ +export enum StatusCode { + // common + archive orders + New = "created", + Pending = "pending", + Processing = "processing", + PostProcessing = "post-processing", + Complete = "complete", + // custom orders + Manual = "manual", + // tasking orders + PendingApproval = "pending-approval", + Rejected = "rejected", + Failed = "failed", + Rescheduled = "rescheduled", + Cancelled = "cancelled" +} + +export function isStatusCode(token: string): token is StatusCode { + return Object.values(StatusCode).includes(token as StatusCode); +} diff --git a/src/tasking/index.ts b/src/tasking/index.ts index be2ae3c..d02a0aa 100644 --- a/src/tasking/index.ts +++ b/src/tasking/index.ts @@ -1,4 +1,4 @@ -import Order, { fromJSON as OrderFromJSON } from "../orders/order"; +import Order, { fromJSON as OrderFromJSON } from "../orders/dataset"; import paths from "../util/paths"; import { jsonOrError, requestBuilder } from "../util/request"; import TaskingSearchRequest from "./search-request"; From 483519ca86bf56d21928bd25d365d98b1642d5a4 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 11:37:22 +1000 Subject: [PATCH 02/30] boiler plate for new types order->dataset +campaign +order --- src/orders/campaign.ts | 239 +++++++++++++++++++++++++++++++++++++++++ src/orders/dataset.ts | 195 ++++++++++++++++++++++++++------- src/orders/order.ts | 120 +++++++++++++++++++++ src/orders/resource.ts | 30 +++--- 4 files changed, 532 insertions(+), 52 deletions(-) create mode 100644 src/orders/campaign.ts create mode 100644 src/orders/order.ts diff --git a/src/orders/campaign.ts b/src/orders/campaign.ts new file mode 100644 index 0000000..52cbc66 --- /dev/null +++ b/src/orders/campaign.ts @@ -0,0 +1,239 @@ +import paths from "../util/paths"; +import { jsonOrError, requestBuilder } from "../util/request"; +import Dataset from "./dataset"; +import { isStatusCode, StatusCode } from "./status"; +import decodePolygon from "../archive/search/polygon"; + +export function fromJSON(client: requestBuilder, json: string|{[key: string]: unknown}): Campaign|string { + if (typeof json === "string") { + json = JSON.parse(json); + } + + if (!(json instanceof Object)) { + return "JSON does not correspond to a Campaign object"; + } + + if (typeof json.id !== "string") { + return "No ID for campaign"; + } + if (typeof json.createdAt !== "string") { + return "No creation date for campaign"; + } + if (typeof json.updatedAt !== "string") { + return "No update date for campaign"; + } + if (typeof json.status !== "string") { + return "Campaign status missing"; + } + if (!isStatusCode(json.status)) { + return `Campaign status '${json.status}' not recognized`; + } + if (typeof json.orderingID !== "string") { + return "Campaign orderingID missing"; + } + if (typeof json.bundle !== "string") { + return "Campaign bundle missing"; + } + if (typeof json.license !== "string") { + return "Campaign license missing"; + } + if (typeof json.priority !== "string") { + return "Campaign priority missing"; + } + if (typeof json.total !== "number") { + return "Campaign total missing"; + } + if (typeof json.discount !== "number") { + return "Campaign discount missing"; + } + if (typeof json.tax !== "number") { + return "Campaign tax missing"; + } + if (!json.refunded) { + json.refunded = 0; + } + if (typeof json.refunded !== "number") { + return "Campaign refund missing"; + } + if (typeof json.order !== "string") { + return "Campaign order missing"; + } + let site = ""; + if (typeof json.site === "string") { + site = json.site; + } + if (typeof json.start !== "string") { + return "Campaign missing start date"; + } + if (typeof json.end !== "string") { + return "Campaign missing end date"; + } + if (!(json?.aoi)) { + return "Campaign missing AOI"; + } + const polygon = decodePolygon(json.aoi) + if (!polygon) { + return "Campaign AOI incorrectly structured"; + } + if (typeof json.cloud !== "number") { + return "Campaign missing cloud coverage"; + } + if (typeof json.offNadir !== "number") { + return "Campaign missing off nadir angle"; + } + if (typeof json.supplier !== "string") { + return "Campaign missing supplier"; + } + const platforms: string[] = []; + if (!Array.isArray(json.platforms)) { + return "Campaign platforms list is not an array"; + } + for (let i=0; i} The list of resources for this order + * @returns {Promise} The list of resources for this dataset */ loadResources(): Promise { - // incomplete orders don't have resources yet + // incomplete datasets don't have resources yet if (this.status !== StatusCode.Complete) { return Promise.resolve(this._resources); } @@ -193,17 +314,17 @@ export default class Dataset { .then(jsonOrError) .then((resp) => { if (typeof resp !== "object") { - return Promise.reject("Order response is not an object"); + return Promise.reject("Dataset response is not an object"); } if (!resp) { - return Promise.reject("Order response is not an object") + return Promise.reject("Dataset response is not an object") } // JSON structure not known, use iterable form const r = (resp as {[key: string]: unknown}) if (!("resources" in r)) { - // order has no resources + // dataset has no resources this.detailed = true; return []; } diff --git a/src/orders/order.ts b/src/orders/order.ts new file mode 100644 index 0000000..9278d08 --- /dev/null +++ b/src/orders/order.ts @@ -0,0 +1,120 @@ +import paths from "../util/paths"; +import { jsonOrError, requestBuilder } from "../util/request"; +import Campaign, { fromJSON as campaignFromJSON } from "./campaign"; +import Dataset, { fromJSON as datasetFromJSON } from "./dataset"; +import { isStatusCode, StatusCode } from "./status"; + +export function fromJSON(client: requestBuilder, json: string|{[key: string]: unknown}): Order|string { + if (typeof json === "string") { + json = JSON.parse(json); + } + + if (!(json instanceof Object)) { + return "JSON does not correspond to an Order object"; + } + + if (typeof json.id !== "string") { + return "No ID for order"; + } + if (typeof json.createdAt !== "string") { + return "No creation date for order"; + } + if (typeof json.updatedAt !== "string") { + return "No update date for order"; + } + if (typeof json.status !== "string") { + return "Order status missing"; + } + if (!isStatusCode(json.status)) { + return `Order status '${json.status}' not recognized`; + } + if (typeof json.total !== "number") { + return "Missing order total"; + } + if (typeof json.discount !== "number") { + return "Missing order discount"; + } + if (typeof json.tax !== "number") { + return "Missing order tax amount"; + } + let monitor = "" + if (typeof json.monitor === "string") { + monitor = json.monitor; + } + + const campaigns: Campaign[] = []; + if (json.resources && Array.isArray(json.campaigns)) { + for (let i=0; i} the content of the resource as a Buffer @@ -164,7 +164,7 @@ export default class Resource { * Download the content of a resource (imagery, metadata, etc) * Data is piped into the provided fs.WriteStream or one is created at the provided filepath. * - * Note: If the order this resource is for has its `expiration` field set and that date has + * Note: If the dataset this resource is for has its `expiration` field set and that date has * passed, this request will fail as the resource has expired and is no longer hosted in the platform * * @returns {Promise} file the resource was written to From efa4842fbf8a58c1d25fb8c2a7ee08fe639dfae1 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 15:33:53 +1000 Subject: [PATCH 03/30] mocking new order endpoints and path scheme --- src/orders/index.ts | 148 ++++++++++++++++++++++++++++++++++++++--- src/orders/order.ts | 12 +++- src/orders/resource.ts | 4 +- src/util/paths.ts | 36 ++++++++-- 4 files changed, 181 insertions(+), 19 deletions(-) diff --git a/src/orders/index.ts b/src/orders/index.ts index 24ee8f8..4b59176 100644 --- a/src/orders/index.ts +++ b/src/orders/index.ts @@ -1,6 +1,8 @@ import { WriteStream } from "fs"; -import Dataset, { fromJSON } from "./dataset"; -import { downloadHelper as resourceDownloader, downloadFileHelper } from "./resource"; +import Order, { fromJSON as orderFromJSON } from "./order"; +import Campaign, { fromJSON as campaignFromJSON } from "./campaign"; +import Dataset, { fromJSON as datasetFromJSON } from "./dataset"; +import Resource, { fromJSON as resourceFromJSON, downloadHelper as resourceDownloader, downloadFileHelper } from "./resource"; import paths from "../util/paths"; import { jsonOrError, requestBuilder } from "../util/request"; @@ -28,18 +30,18 @@ export default class Orders { * or * @see {https://arlula.com/documentation/#ref-order|Order structure reference} */ - list(): Promise { - return this._client("GET", paths.OrderList) + orderList(): Promise { + return this._client("GET", paths.OrderList()) .then(jsonOrError) .then((resp) => { if (!Array.isArray(resp)) { return Promise.reject("Orders list response is not array"); } - const orders: Dataset[] = []; + const orders: Order[] = []; for (let i=0; i} the list of tasking campaigns + * + * @see {https://arlula.com/documentation/#campaign-list|Campaign List endpoint documentation} + * or + * @see {https://arlula.com/documentation/#ref-campaign|Campaign structure reference} + */ + campaignList(): Promise { + return this._client("GET", paths.CampaignList()) + .then(jsonOrError) + .then((resp) => { + if (!Array.isArray(resp)) { + return Promise.reject("Campaigns list response is not array"); + } + + const campaigns: Campaign[] = []; + for (let i=0; i} the list of datasets + * + * @see {https://arlula.com/documentation/#dataset-list|Dataset List endpoint documentation} + * or + * @see {https://arlula.com/documentation/#ref-dataset|Dataset structure reference} + */ + datasetList(): Promise { + return this._client("GET", paths.DatasetList()) + .then(jsonOrError) + .then((resp) => { + if (!Array.isArray(resp)) { + return Promise.reject("Datasets list response is not array"); + } + + const datasets: Dataset[] = []; + for (let i=0; i { - return this._client("GET", paths.OrderGet+"?id="+id) + getOrder(id: string): Promise { + return this._client("GET", paths.OrderGet(id)) .then(jsonOrError) .then((resp) => { if (typeof resp !== "object") { return Promise.reject("Order is not an object"); } - const ord = fromJSON(this._client, resp as {[key: string]: unknown}); + const ord = orderFromJSON(this._client, resp as {[key: string]: unknown}); + if (!(ord instanceof Order)) { + return Promise.reject(ord); + } + return ord; + }); + } + + /** + * Gets a specific tasking campaign from the server from its ID + * @param {string} id the ID of the campaign to retrieve + * @returns {Promise} the campaign retrieved + * + * @see {https://arlula.com/documentation/#campaign-get|Campaign Get endpoint documentation} + * or + * @see {https://arlula.com/documentation/#ref-campaign|Campaign structure reference} + */ + getCampaign(id: string): Promise { + return this._client("GET", paths.CampaignGet(id)) + .then(jsonOrError) + .then((resp) => { + if (typeof resp !== "object") { + return Promise.reject("Campaign is not an object"); + } + + const ord = campaignFromJSON(this._client, resp as {[key: string]: unknown}); + if (!(ord instanceof Campaign)) { + return Promise.reject(ord); + } + return ord; + }); + } + + /** + * Gets a specific dataset from the server from its ID + * @param {string} id the ID of the dataset to retrieve + * @returns {Promise} the dataset retrieved + * + * @see {https://arlula.com/documentation/#dataset-get|Dataset Get endpoint documentation} + * or + * @see {https://arlula.com/documentation/#ref-dataset|Dataset structure reference} + */ + getDataset(id: string): Promise { + return this._client("GET", paths.DatasetGet(id)) + .then(jsonOrError) + .then((resp) => { + if (typeof resp !== "object") { + return Promise.reject("Dataset is not an object"); + } + + const ord = datasetFromJSON(this._client, resp as {[key: string]: unknown}); if (!(ord instanceof Dataset)) { return Promise.reject(ord); } @@ -74,6 +186,22 @@ export default class Orders { }); } + getResource(id: string): Promise { + return this._client("GET", paths.ResourceGet(id)) + .then(jsonOrError) + .then((resp) => { + if (typeof resp !== "object") { + return Promise.reject("Resource is not an object"); + } + + const ord = resourceFromJSON(this._client, resp as {[key: string]: unknown}); + if (!(ord instanceof Resource)) { + return Promise.reject(ord); + } + return ord; + }); + } + /** * Download the content of a resource (imagery, metadata, etc) based on its ID * Data is made available as an ArrayBuffer. diff --git a/src/orders/order.ts b/src/orders/order.ts index 9278d08..b4538c1 100644 --- a/src/orders/order.ts +++ b/src/orders/order.ts @@ -37,6 +37,12 @@ export function fromJSON(client: requestBuilder, json: string|{[key: string]: un if (typeof json.tax !== "number") { return "Missing order tax amount"; } + if (!json.refunded) { + json.refunded = 0; + } + if (typeof json.refunded !== "number") { + return "Missing order refunded amount" + } let monitor = "" if (typeof json.monitor === "string") { monitor = json.monitor; @@ -66,7 +72,7 @@ export function fromJSON(client: requestBuilder, json: string|{[key: string]: un } } - return new Order(client, json.id, new Date(json.createdAt), new Date(json.updatedAt), json.status, json.total, json.discount, json.tax, monitor, [], []); + return new Order(client, json.id, new Date(json.createdAt), new Date(json.updatedAt), json.status, json.total, json.discount, json.tax, json.refunded, monitor, [], []); } export default class Order { @@ -78,6 +84,7 @@ export default class Order { private _total: number; private _discount: number; private _tax: number; + private _refunded: number; private _monitor: string; private _campaigns: Campaign[]; private _datasets: Dataset[]; @@ -90,6 +97,7 @@ export default class Order { total: number, discount: number, tax: number, + refunded: number, monitor: string, campaigns: Campaign[], datasets: Dataset[], @@ -102,6 +110,7 @@ export default class Order { this._total = total; this._discount = discount; this._tax = tax; + this._refunded = refunded; this._monitor = monitor; this._campaigns = campaigns; this._datasets = datasets; @@ -114,6 +123,7 @@ export default class Order { public get total(): number {return this._total} public get discount(): number {return this._discount} public get tax(): number {return this._tax} + public get refunded(): number {return this._refunded} public get monitor(): string {return this._monitor} public get campaigns(): Campaign[] {return this._campaigns} public get datasets(): Dataset[] {return this._datasets} diff --git a/src/orders/resource.ts b/src/orders/resource.ts index b1bbbe1..b65fd85 100644 --- a/src/orders/resource.ts +++ b/src/orders/resource.ts @@ -183,7 +183,7 @@ export default class Resource { * @param {string} id ID of the resource to download */ export function downloadHelper(client: requestBuilder, id: string): Promise { - return client("GET", paths.ResourceDownload+"?id="+id) + return client("GET", paths.ResourceDownload(id)) .then((r) => { const redirect = r.headers.get("location"); if (!redirect) { @@ -216,7 +216,7 @@ export function downloadFileHelper(client: requestBuilder, id: string, fileRef: file = fileRef; } - client("GET", paths.ResourceDownload+"?id="+id) + client("GET", paths.ResourceDownload(id)) .then((r) => { const redirect = r.headers.get("location"); if (!redirect) { diff --git a/src/util/paths.ts b/src/util/paths.ts index 8d23fcc..07e02d9 100644 --- a/src/util/paths.ts +++ b/src/util/paths.ts @@ -19,14 +19,38 @@ export default class paths { return host + "/api/archive/order/batch"; } - static get OrderList(): string { - return host + "/api/order/list"; + static OrderList(): string { + return host + "/api/orders"; } - static get OrderGet(): string { - return host + "/api/order/get"; + static CampaignList(): string { + return host + "/api/campaigns"; } - static get ResourceDownload(): string { - return host + "/api/order/resource/get"; + static DatasetList(): string { + return host + "/api/datasets"; + } + static OrderGet(id: string): string { + return host + `/api/order/${id}`; + } + static CampaignGet(id: string): string { + return host + `/api/campaign/${id}`; + } + static DatasetGet(id: string): string { + return host + `/api/dataset/${id}`; + } + static ResourceGet(id: string): string { + return host + `/api/resource/${id}`; + } + static ResourceDownload(id: string): string { + return host + `/api/resource/${id}/data`; + } + static OrderCampaigns(id: string): string { + return `${host}/api/order/${id}/campaigns` + } + static OrderDatasets(id: string): string { + return `${host}/api/order/${id}/datasets` + } + static CampaignDatasets(id: string): string { + return `${host}/api/campaign/${id}/datasets` } static get CollectionConformance(): string { From d8e60e0f92c0dd081dcfb92ead6fae3ff5288312 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 15:39:29 +1000 Subject: [PATCH 04/30] updating documentation comments for endpoint documentation references --- src/orders/index.ts | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/orders/index.ts b/src/orders/index.ts index 4b59176..a1f3376 100644 --- a/src/orders/index.ts +++ b/src/orders/index.ts @@ -26,9 +26,9 @@ export default class Orders { * list orders previously placed by this API from newest to oldest * @returns {Promise} the list of orders * - * @see {https://arlula.com/documentation/#order-list|Orders List endpoint documentation} + * @see {https://arlula.com/documentation/orders/#order-list|Orders List endpoint documentation} * or - * @see {https://arlula.com/documentation/#ref-order|Order structure reference} + * @see {https://arlula.com/documentation/orders/#ref-order|Order structure reference} */ orderList(): Promise { return this._client("GET", paths.OrderList()) @@ -55,9 +55,9 @@ export default class Orders { * list tasking campaigns previously lodged by this API from newest to oldest * @returns {Promise} the list of tasking campaigns * - * @see {https://arlula.com/documentation/#campaign-list|Campaign List endpoint documentation} + * @see {https://arlula.com/documentation/orders/#campaign-list|Campaign List endpoint documentation} * or - * @see {https://arlula.com/documentation/#ref-campaign|Campaign structure reference} + * @see {https://arlula.com/documentation/orders/#ref-campaign|Campaign structure reference} */ campaignList(): Promise { return this._client("GET", paths.CampaignList()) @@ -86,9 +86,9 @@ export default class Orders { * and those generated by delivery from a tasking campaign * @returns {Promise} the list of datasets * - * @see {https://arlula.com/documentation/#dataset-list|Dataset List endpoint documentation} + * @see {https://arlula.com/documentation/orders/#dataset-list|Dataset List endpoint documentation} * or - * @see {https://arlula.com/documentation/#ref-dataset|Dataset structure reference} + * @see {https://arlula.com/documentation/orders/#ref-dataset|Dataset structure reference} */ datasetList(): Promise { return this._client("GET", paths.DatasetList()) @@ -116,9 +116,9 @@ export default class Orders { * @param {string} id the ID of the order to retrieve * @returns {Promise} the order retrieved * - * @see {https://arlula.com/documentation/#order-get|Orders Get endpoint documentation} + * @see {https://arlula.com/documentation/orders/#order-get|Orders Get endpoint documentation} * or - * @see {https://arlula.com/documentation/#ref-order|Order structure reference} + * @see {https://arlula.com/documentation/orders/#ref-order|Order structure reference} */ getOrder(id: string): Promise { return this._client("GET", paths.OrderGet(id)) @@ -141,9 +141,9 @@ export default class Orders { * @param {string} id the ID of the campaign to retrieve * @returns {Promise} the campaign retrieved * - * @see {https://arlula.com/documentation/#campaign-get|Campaign Get endpoint documentation} + * @see {https://arlula.com/documentation/orders/#campaign-get|Campaign Get endpoint documentation} * or - * @see {https://arlula.com/documentation/#ref-campaign|Campaign structure reference} + * @see {https://arlula.com/documentation/orders/#ref-campaign|Campaign structure reference} */ getCampaign(id: string): Promise { return this._client("GET", paths.CampaignGet(id)) @@ -166,9 +166,9 @@ export default class Orders { * @param {string} id the ID of the dataset to retrieve * @returns {Promise} the dataset retrieved * - * @see {https://arlula.com/documentation/#dataset-get|Dataset Get endpoint documentation} + * @see {https://arlula.com/documentation/orders/#dataset-get|Dataset Get endpoint documentation} * or - * @see {https://arlula.com/documentation/#ref-dataset|Dataset structure reference} + * @see {https://arlula.com/documentation/orders/#ref-dataset|Dataset structure reference} */ getDataset(id: string): Promise { return this._client("GET", paths.DatasetGet(id)) @@ -186,6 +186,15 @@ export default class Orders { }); } + /** + * Get a specific resource from a cached ID to access its roles and details + * @param {string} id the ID of the resource to retrieve + * @returns {Promise} the resource retrieved + * + * @see {https://arlula.com/documentation/orders/#resource-get|Resource Get endpoint documentation} + * or + * @see {https://arlula.com/documentation/orders/#ref-resource|Resource structure reference} + */ getResource(id: string): Promise { return this._client("GET", paths.ResourceGet(id)) .then(jsonOrError) @@ -212,9 +221,9 @@ export default class Orders { * @param {string} id The ID of the resource to download * @returns {Promise} the content of the resource as a Buffer * - * @see {https://arlula.com/documentation/#order-resource|Order Resource Get endpoint documentation} + * @see {https://arlula.com/documentation/orders/#order-resource|Order Resource Get endpoint documentation} * or - * @see {https://arlula.com/documentation/#ref-resource|Order Resource structure reference} + * @see {https://arlula.com/documentation/orders/#ref-resource|Order Resource structure reference} */ downloadResource(id: string): Promise { return resourceDownloader(this._client, id); @@ -230,9 +239,9 @@ export default class Orders { * @param {string} id The ID of the resource to download * @returns {Promise} file the resource was written to * - * @see {https://arlula.com/documentation/#order-resource|Order Resource Get endpoint documentation} + * @see {https://arlula.com/documentation/orders/#order-resource|Order Resource Get endpoint documentation} * or - * @see {https://arlula.com/documentation/#ref-resource|Order Resource structure reference} + * @see {https://arlula.com/documentation/orders/#ref-resource|Order Resource structure reference} */ downloadResourceToFile(id: string, ref: string|WriteStream): Promise { return downloadFileHelper(this._client, id, ref); From f12dee4601d693d6d21dbb4879832f8878a537ed Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 16:32:26 +1000 Subject: [PATCH 05/30] updating list endpoints to have new pagination structure --- src/orders/index.ts | 107 +++++++++++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 36 deletions(-) diff --git a/src/orders/index.ts b/src/orders/index.ts index a1f3376..f54d89f 100644 --- a/src/orders/index.ts +++ b/src/orders/index.ts @@ -24,59 +24,51 @@ export default class Orders { /** * list orders previously placed by this API from newest to oldest - * @returns {Promise} the list of orders + * @returns {Promise>} the list of orders * * @see {https://arlula.com/documentation/orders/#order-list|Orders List endpoint documentation} * or * @see {https://arlula.com/documentation/orders/#ref-order|Order structure reference} */ - orderList(): Promise { + orderList(): Promise> { return this._client("GET", paths.OrderList()) .then(jsonOrError) .then((resp) => { - if (!Array.isArray(resp)) { - return Promise.reject("Orders list response is not array"); + if (!(resp instanceof Object)) { + return Promise.reject("Order list endpoint returned a malformed response"); } - const orders: Order[] = []; - for (let i=0; i(this._client, resp as {[key: string]: unknown}, orderFromJSON) + if (typeof list === "string") { + return Promise.reject(list); } - return orders; + return list; }); } /** * list tasking campaigns previously lodged by this API from newest to oldest - * @returns {Promise} the list of tasking campaigns + * @returns {Promise>} the list of tasking campaigns * * @see {https://arlula.com/documentation/orders/#campaign-list|Campaign List endpoint documentation} * or * @see {https://arlula.com/documentation/orders/#ref-campaign|Campaign structure reference} */ - campaignList(): Promise { + campaignList(): Promise> { return this._client("GET", paths.CampaignList()) .then(jsonOrError) .then((resp) => { - if (!Array.isArray(resp)) { - return Promise.reject("Campaigns list response is not array"); + if (!(resp instanceof Object)) { + return Promise.reject("Campaign list endpoint returned a malformed response"); } - const campaigns: Campaign[] = []; - for (let i=0; i(this._client, resp as {[key: string]: unknown}, campaignFromJSON) + if (typeof list === "string") { + return Promise.reject(list); } - return campaigns; + return list; }); } @@ -84,30 +76,26 @@ export default class Orders { * list datasets available to this API from newest to oldest. * this will include both those created by archive orders, * and those generated by delivery from a tasking campaign - * @returns {Promise} the list of datasets + * @returns {Promise>} the list of datasets * * @see {https://arlula.com/documentation/orders/#dataset-list|Dataset List endpoint documentation} * or * @see {https://arlula.com/documentation/orders/#ref-dataset|Dataset structure reference} */ - datasetList(): Promise { + datasetList(): Promise> { return this._client("GET", paths.DatasetList()) .then(jsonOrError) .then((resp) => { - if (!Array.isArray(resp)) { - return Promise.reject("Datasets list response is not array"); + if (!(resp instanceof Object)) { + return Promise.reject("Campaign list endpoint returned a malformed response"); } - const datasets: Dataset[] = []; - for (let i=0; i(this._client, resp as {[key: string]: unknown}, datasetFromJSON) + if (typeof list === "string") { + return Promise.reject(list); } - return datasets; + return list; }); } @@ -247,3 +235,50 @@ export default class Orders { return downloadFileHelper(this._client, id, ref); } } + +export interface ListResponse { + content: Type[]; + page: number; + length: number; + count: number; +} + +function parseListResponse(client: requestBuilder, json: string|{[key: string]: unknown}, fromJSON: (client: requestBuilder, json: string|{[key: string]: unknown})=>Type|string): ListResponse|string { + if (typeof json === "string") { + json = JSON.parse(json); + } + + if (!(json instanceof Object)) { + return "JSON does not correspond to an Order object"; + } + + if (typeof json.page != "number") { + return "list response is missing page number" + } + if (typeof json.length != "number") { + return "list response is missing page length" + } + if (typeof json.count != "number") { + return "list response is missing result count" + } + if (!Array.isArray(json.content)) { + return "list response content is not an array" + } + + const resp: ListResponse = { + content: [], + page: json.page, + length: json.length, + count: json.count, + }; + + for (let i=0; i Date: Tue, 13 Aug 2024 16:33:29 +1000 Subject: [PATCH 06/30] adding pagination to list endpoints --- src/orders/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/orders/index.ts b/src/orders/index.ts index f54d89f..d418ac3 100644 --- a/src/orders/index.ts +++ b/src/orders/index.ts @@ -30,8 +30,8 @@ export default class Orders { * or * @see {https://arlula.com/documentation/orders/#ref-order|Order structure reference} */ - orderList(): Promise> { - return this._client("GET", paths.OrderList()) + orderList(page?: number): Promise> { + return this._client("GET", paths.OrderList()+(page ? `?page=${page}` : "")) .then(jsonOrError) .then((resp) => { if (!(resp instanceof Object)) { @@ -55,8 +55,8 @@ export default class Orders { * or * @see {https://arlula.com/documentation/orders/#ref-campaign|Campaign structure reference} */ - campaignList(): Promise> { - return this._client("GET", paths.CampaignList()) + campaignList(page?: number): Promise> { + return this._client("GET", paths.CampaignList()+(page ? `?page=${page}` : "")) .then(jsonOrError) .then((resp) => { if (!(resp instanceof Object)) { @@ -82,8 +82,8 @@ export default class Orders { * or * @see {https://arlula.com/documentation/orders/#ref-dataset|Dataset structure reference} */ - datasetList(): Promise> { - return this._client("GET", paths.DatasetList()) + datasetList(page?: number): Promise> { + return this._client("GET", paths.DatasetList()+(page ? `?page=${page}` : "")) .then(jsonOrError) .then((resp) => { if (!(resp instanceof Object)) { From 95c0d8838bdb8d729110e57d713d2f928d224df7 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 16:42:44 +1000 Subject: [PATCH 07/30] separating list parsing into utility --- src/orders/index.ts | 48 +-------------------------------------------- src/orders/lists.ts | 48 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 47 deletions(-) create mode 100644 src/orders/lists.ts diff --git a/src/orders/index.ts b/src/orders/index.ts index d418ac3..58e32a5 100644 --- a/src/orders/index.ts +++ b/src/orders/index.ts @@ -5,6 +5,7 @@ import Dataset, { fromJSON as datasetFromJSON } from "./dataset"; import Resource, { fromJSON as resourceFromJSON, downloadHelper as resourceDownloader, downloadFileHelper } from "./resource"; import paths from "../util/paths"; import { jsonOrError, requestBuilder } from "../util/request"; +import { ListResponse, parseListResponse } from "./lists"; /** * @class Orders wraps the API requests to the order management API @@ -235,50 +236,3 @@ export default class Orders { return downloadFileHelper(this._client, id, ref); } } - -export interface ListResponse { - content: Type[]; - page: number; - length: number; - count: number; -} - -function parseListResponse(client: requestBuilder, json: string|{[key: string]: unknown}, fromJSON: (client: requestBuilder, json: string|{[key: string]: unknown})=>Type|string): ListResponse|string { - if (typeof json === "string") { - json = JSON.parse(json); - } - - if (!(json instanceof Object)) { - return "JSON does not correspond to an Order object"; - } - - if (typeof json.page != "number") { - return "list response is missing page number" - } - if (typeof json.length != "number") { - return "list response is missing page length" - } - if (typeof json.count != "number") { - return "list response is missing result count" - } - if (!Array.isArray(json.content)) { - return "list response content is not an array" - } - - const resp: ListResponse = { - content: [], - page: json.page, - length: json.length, - count: json.count, - }; - - for (let i=0; i { + content: Type[]; + page: number; + length: number; + count: number; +} + +export function parseListResponse(client: requestBuilder, json: string|{[key: string]: unknown}, fromJSON: (client: requestBuilder, json: string|{[key: string]: unknown})=>Type|string): ListResponse|string { + if (typeof json === "string") { + json = JSON.parse(json); + } + + if (!(json instanceof Object)) { + return "JSON does not correspond to an Order object"; + } + + if (typeof json.page != "number") { + return "list response is missing page number" + } + if (typeof json.length != "number") { + return "list response is missing page length" + } + if (typeof json.count != "number") { + return "list response is missing result count" + } + if (!Array.isArray(json.content)) { + return "list response content is not an array" + } + + const resp: ListResponse = { + content: [], + page: json.page, + length: json.length, + count: json.count, + }; + + for (let i=0; i Date: Tue, 13 Aug 2024 16:43:44 +1000 Subject: [PATCH 08/30] campaign get datasets getter (and retrieve from endpoint --- src/orders/campaign.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/orders/campaign.ts b/src/orders/campaign.ts index 52cbc66..e606d48 100644 --- a/src/orders/campaign.ts +++ b/src/orders/campaign.ts @@ -1,8 +1,9 @@ import paths from "../util/paths"; import { jsonOrError, requestBuilder } from "../util/request"; -import Dataset from "./dataset"; +import Dataset, { fromJSON as datasetFromJSON } from "./dataset"; import { isStatusCode, StatusCode } from "./status"; import decodePolygon from "../archive/search/polygon"; +import { parseListResponse } from "./lists" export function fromJSON(client: requestBuilder, json: string|{[key: string]: unknown}): Campaign|string { if (typeof json === "string") { @@ -236,4 +237,25 @@ export default class Campaign { public get supplier(): string {return this._supplier} public get platforms(): string[] {return this._platforms} public get gsd(): number {return this._gsd} + + public get datasets(): Promise { + if (this._datasets.length) { + return Promise.resolve(this._datasets); + } + + return this._client("GET", paths.CampaignDatasets(this._id)) + .then(jsonOrError) + .then((resp) => { + if (!(resp instanceof Object)) { + return Promise.reject("Campaign dataset list endpoint returned a malformed response"); + } + + const list = parseListResponse(this._client, resp as {[key: string]: unknown}, datasetFromJSON) + if (typeof list === "string") { + return Promise.reject(list); + } + + return list.content; + }); + } } \ No newline at end of file From c7dffc8170c9ea024549cb1444c7dfb4086561bd Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 16:58:41 +1000 Subject: [PATCH 09/30] caching campaign datasets --- src/orders/campaign.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/orders/campaign.ts b/src/orders/campaign.ts index e606d48..1737338 100644 --- a/src/orders/campaign.ts +++ b/src/orders/campaign.ts @@ -164,6 +164,7 @@ export default class Campaign { // delivered data private _datasets: Dataset[] = []; + private _attemptedDatasets = false; constructor( client: requestBuilder, @@ -189,6 +190,7 @@ export default class Campaign { supplier: string, platforms: string[], gsd: number, + datasets: Dataset[], ) { this._client = client; this._id = id; @@ -213,6 +215,8 @@ export default class Campaign { this._supplier = supplier; this._platforms = platforms; this._gsd = gsd; + this._datasets = datasets; + this._attemptedDatasets = this._datasets.length > 0; } public get id(): string {return this._id} @@ -239,7 +243,7 @@ export default class Campaign { public get gsd(): number {return this._gsd} public get datasets(): Promise { - if (this._datasets.length) { + if (this._attemptedDatasets) { return Promise.resolve(this._datasets); } @@ -255,6 +259,9 @@ export default class Campaign { return Promise.reject(list); } + this._datasets = list.content; + this._attemptedDatasets = true; + return list.content; }); } From 0cc59f3cac3cc605e6e67aefb83067b2982e8b48 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 16:58:52 +1000 Subject: [PATCH 10/30] decoding campaign datasets in from json --- src/orders/campaign.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/orders/campaign.ts b/src/orders/campaign.ts index 1737338..8a16a3e 100644 --- a/src/orders/campaign.ts +++ b/src/orders/campaign.ts @@ -99,6 +99,18 @@ export function fromJSON(client: requestBuilder, json: string|{[key: string]: un return "Campaign missing GSD"; } + const datasets: Dataset[] = []; + if (json.resources && Array.isArray(json.datasets)) { + for (let i=0; i Date: Tue, 13 Aug 2024 16:59:15 +1000 Subject: [PATCH 11/30] providing campaign and dataset list getters for orders --- src/orders/order.ts | 54 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/orders/order.ts b/src/orders/order.ts index b4538c1..ab723a3 100644 --- a/src/orders/order.ts +++ b/src/orders/order.ts @@ -3,6 +3,7 @@ import { jsonOrError, requestBuilder } from "../util/request"; import Campaign, { fromJSON as campaignFromJSON } from "./campaign"; import Dataset, { fromJSON as datasetFromJSON } from "./dataset"; import { isStatusCode, StatusCode } from "./status"; +import { parseListResponse } from "./lists" export function fromJSON(client: requestBuilder, json: string|{[key: string]: unknown}): Order|string { if (typeof json === "string") { @@ -88,6 +89,8 @@ export default class Order { private _monitor: string; private _campaigns: Campaign[]; private _datasets: Dataset[]; + private _attemptedCampaigns = false; + private _attemptedDatasets = false; constructor( client: requestBuilder, id: string, @@ -114,6 +117,8 @@ export default class Order { this._monitor = monitor; this._campaigns = campaigns; this._datasets = datasets; + this._attemptedCampaigns = this._campaigns.length > 0; + this._attemptedDatasets = this._datasets.length > 0; } public get id(): string {return this._id} @@ -125,6 +130,51 @@ export default class Order { public get tax(): number {return this._tax} public get refunded(): number {return this._refunded} public get monitor(): string {return this._monitor} - public get campaigns(): Campaign[] {return this._campaigns} - public get datasets(): Dataset[] {return this._datasets} + + public get campaigns(): Promise { + if (this._attemptedCampaigns) { + return Promise.resolve(this._campaigns); + } + + return this._client("GET", paths.CampaignDatasets(this._id)) + .then(jsonOrError) + .then((resp) => { + if (!(resp instanceof Object)) { + return Promise.reject("Campaign dataset list endpoint returned a malformed response"); + } + + const list = parseListResponse(this._client, resp as {[key: string]: unknown}, campaignFromJSON) + if (typeof list === "string") { + return Promise.reject(list); + } + + this._campaigns = list.content; + this._attemptedCampaigns = true; + + return list.content; + }); + } + public get datasets(): Promise { + if (this._attemptedDatasets) { + return Promise.resolve(this._datasets); + } + + return this._client("GET", paths.CampaignDatasets(this._id)) + .then(jsonOrError) + .then((resp) => { + if (!(resp instanceof Object)) { + return Promise.reject("Campaign dataset list endpoint returned a malformed response"); + } + + const list = parseListResponse(this._client, resp as {[key: string]: unknown}, datasetFromJSON) + if (typeof list === "string") { + return Promise.reject(list); + } + + this._datasets = list.content; + this._attemptedDatasets = true; + + return list.content; + }); + } } \ No newline at end of file From da7e204d531a05c0b6316eaccec8fd7191212958 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 16:59:54 +1000 Subject: [PATCH 12/30] fixing path's in order sub list calls --- src/orders/order.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/orders/order.ts b/src/orders/order.ts index ab723a3..2731842 100644 --- a/src/orders/order.ts +++ b/src/orders/order.ts @@ -136,7 +136,7 @@ export default class Order { return Promise.resolve(this._campaigns); } - return this._client("GET", paths.CampaignDatasets(this._id)) + return this._client("GET", paths.OrderCampaigns(this._id)) .then(jsonOrError) .then((resp) => { if (!(resp instanceof Object)) { @@ -159,7 +159,7 @@ export default class Order { return Promise.resolve(this._datasets); } - return this._client("GET", paths.CampaignDatasets(this._id)) + return this._client("GET", paths.OrderDatasets(this._id)) .then(jsonOrError) .then((resp) => { if (!(resp instanceof Object)) { From 2a7b7b75c7915ff8be4c83031580d4f0bda2571a Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Tue, 13 Aug 2024 17:03:01 +1000 Subject: [PATCH 13/30] order endpoints now return the new order structure --- src/archive/index.ts | 23 +++++++---------------- src/tasking/index.ts | 23 +++++++---------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/src/archive/index.ts b/src/archive/index.ts index a4e0046..09766dc 100644 --- a/src/archive/index.ts +++ b/src/archive/index.ts @@ -1,6 +1,6 @@ import SearchRequest from "./search-request"; import SearchResponse, { decodeResponse, decodeResultSet } from "./search/response"; -import Order, { fromJSON as OrderFromJSON } from "../orders/dataset"; +import Order, { fromJSON as orderFromJSON } from "../orders/order"; import OrderRequest from "../orders/order-request"; import paths from "../util/paths"; import { jsonOrError, requestBuilder } from "../util/request"; @@ -84,7 +84,7 @@ export default class Archive { .then(jsonOrError) .then((resp) => { - const ord = OrderFromJSON(this._client, resp as {[key: string]: unknown}); + const ord = orderFromJSON(this._client, resp as {[key: string]: unknown}); if (!(ord instanceof Order)) { return Promise.reject(ord); } @@ -93,7 +93,7 @@ export default class Archive { }); } - batchOrder(req: BatchOrderRequest): Promise { + batchOrder(req: BatchOrderRequest): Promise { if (!req.valid()) { return Promise.reject("invalid order request"); } @@ -103,21 +103,12 @@ export default class Archive { .then(jsonOrError) .then((resp) => { - if (!Array.isArray(resp)) { - return Promise.reject("error placing batch order, response is not array or orders") - } - - const ords: Order[] = []; - - for (let i=0; i { - const ord = OrderFromJSON(this._client, resp as {[key: string]: unknown}); + const ord = orderFromJSON(this._client, resp as {[key: string]: unknown}); if (!(ord instanceof Order)) { return Promise.reject(ord); } @@ -86,7 +86,7 @@ export default class Tasking { }); } - batchOrder(req: BatchOrderRequest): Promise { + batchOrder(req: BatchOrderRequest): Promise { if (!req.valid()) { return Promise.reject("invalid order request"); } @@ -94,21 +94,12 @@ export default class Tasking { .then(jsonOrError) .then((resp) => { - if (!Array.isArray(resp)) { - return Promise.reject("error placing batch order, response is not array or orders") - } - - const ords: Order[] = []; - - for (let i=0; i Date: Tue, 13 Aug 2024 18:08:51 +1000 Subject: [PATCH 14/30] handling new search response structure --- src/tasking/cloud.ts | 61 ++++++++++++++++++++ src/tasking/priority.ts | 61 ++++++++++++++++++++ src/tasking/search-response.ts | 101 +++++++++++++++++++++++++-------- 3 files changed, 198 insertions(+), 25 deletions(-) create mode 100644 src/tasking/cloud.ts create mode 100644 src/tasking/priority.ts diff --git a/src/tasking/cloud.ts b/src/tasking/cloud.ts new file mode 100644 index 0000000..5f4baaa --- /dev/null +++ b/src/tasking/cloud.ts @@ -0,0 +1,61 @@ + +export function decodeCloudLevel(json: unknown): CloudLevel|null { + let max = 0; + let name = ""; + let description = ""; + let percent = 0; + let amount = 0; + + if (typeof json !== "object") { + return null; + } + + const argMap = json as {[key: string]: unknown}; + + if (argMap?.max && typeof argMap.max == "number") { + max = argMap.max; + } + + if (argMap?.name && typeof argMap.name == "string") { + name = argMap.name; + } + + if (argMap?.description && typeof argMap.description == "string") { + description = argMap.description; + } + + if (argMap?.loadingPercent && typeof argMap.loadingPercent == "number") { + percent = argMap.loadingPercent; + } + + if (argMap?.loadingAmount && typeof argMap.loadingAmount == "number") { + amount = argMap.loadingAmount; + } + + if (!(max||name||description||percent||amount)) { + return null; + } + return new CloudLevel(max, name, description, percent, amount); +} + +export class CloudLevel { + max: number; + name: string; + description: string; + loadingPercent: number; + loadingAmount: number; + constructor( + max: number, + name: string, + description: string, + loadingPercent: number, + loadingAmount: number, + ) { + this.max = max; + this.name = name; + this.description = description; + this.loadingPercent = loadingPercent; + this.loadingAmount = loadingAmount; + } + +} diff --git a/src/tasking/priority.ts b/src/tasking/priority.ts new file mode 100644 index 0000000..b2a5a8b --- /dev/null +++ b/src/tasking/priority.ts @@ -0,0 +1,61 @@ + +export function decodePriority(json: unknown): Priority|null { + let key = ""; + let name = ""; + let description = ""; + let percent = 0; + let amount = 0; + + if (typeof json !== "object") { + return null; + } + + const argMap = json as {[key: string]: unknown}; + + if (argMap?.key && typeof argMap.key == "string") { + key = argMap.key; + } + + if (argMap?.name && typeof argMap.name == "string") { + name = argMap.name; + } + + if (argMap?.description && typeof argMap.description == "string") { + description = argMap.description; + } + + if (argMap?.loadingPercent && typeof argMap.loadingPercent == "number") { + percent = argMap.loadingPercent; + } + + if (argMap?.loadingAmount && typeof argMap.loadingAmount == "number") { + amount = argMap.loadingAmount; + } + + if (!(key||name||description||percent||amount)) { + return null; + } + return new Priority(key, name, description, percent, amount); +} + +export class Priority { + key: string; + name: string; + description: string; + loadingPercent: number; + loadingAmount: number; + constructor( + key: string, + name: string, + description: string, + loadingPercent: number, + loadingAmount: number, + ) { + this.key = key; + this.name = name; + this.description = description; + this.loadingPercent = loadingPercent; + this.loadingAmount = loadingAmount; + } + +} diff --git a/src/tasking/search-response.ts b/src/tasking/search-response.ts index d71e5f5..97e3c63 100644 --- a/src/tasking/search-response.ts +++ b/src/tasking/search-response.ts @@ -2,9 +2,11 @@ import Band from "../archive/search/band"; import BundleOption from "../archive/search/bundle"; import License from "../archive/search/license"; import { decodeBand } from "../archive/search/band"; -import { decodeMultiPolygon } from "../archive/search/polygon"; +import decodePolygon from "../archive/search/polygon"; import { decodeBundle } from "../archive/search/bundle"; import { decodeLicense } from "../archive/search/license"; +import { Priority, decodePriority } from "./priority"; +import { CloudLevel, decodeCloudLevel } from "./cloud"; /** * TaskingSearchResponse is a response envelope that includes the search result set @@ -18,7 +20,8 @@ export interface TaskingSearchResponse { // status: string; errors?: annotation[]; // warnings: string[]; - results?: TaskingSearchResult[]; + results?: TaskingSearchResult[]; + failures?: taskingFailure[]; } @@ -28,8 +31,16 @@ export interface annotation { platforms?: string[]; } +export interface taskingFailure { + type: string; + message: string; + supplier: string; + platforms: string[]; + detail: any; +} + export function isResponse(object: unknown): object is TaskingSearchResponse { - return !!object && (typeof object === "object") && ('errors' in object || 'results' in object); + return !!object && (typeof object === "object") && ('errors' in object || 'results' in object || 'failures' in object); } // decodeResponse is a helper for reading results from JSON, it is not intended for public use. @@ -84,10 +95,10 @@ export function decodeResultSet(json: unknown[]): TaskingSearchResult[]|null { * @see {https://arlula.com/documentation/#ref-search-result|Tasking Search result structure reference} */ export class TaskingSearchResult { - polygons: number[][][][]; - areas: taskingArea; + polygon: number[][][]; startDate: Date; endDate: Date; + metrics: taskingMetrics; gsd: number; supplier: string; orderingID: string; @@ -95,13 +106,15 @@ export class TaskingSearchResult { bands: Band[]; bundles: BundleOption[]; licenses: License[]; + priorities: Priority[]; + cloud: CloudLevel[]; platforms: string[]; annotations?: annotation[]; constructor( - polygons: number[][][][], - areas: taskingArea, + polygon: number[][][], startDate: Date, endDate: Date, + metrics: taskingMetrics, gsd: number, supplier: string, orderingID: string, @@ -109,11 +122,13 @@ export class TaskingSearchResult { bands: Band[], bundles: BundleOption[], licenses: License[], + priorities: Priority[], + cloud: CloudLevel[], platforms: string[], annotations?: annotation[], ) { - this.polygons = polygons; - this.areas = areas; + this.polygon = polygon; + this.metrics = metrics; this.startDate = startDate; this.endDate = endDate; this.gsd = gsd; @@ -123,27 +138,33 @@ export class TaskingSearchResult { this.bands = bands; this.bundles = bundles; this.licenses = licenses; + this.priorities = priorities; + this.cloud = cloud; this.platforms = platforms; this.annotations = annotations; } } -export interface taskingArea { - target: number; - scene: number; +export interface taskingMetrics { + windowsAvailable: number; + windowsRequired: number; + orderArea: number; + moq: number; } // decodeResult is a helper for reading results from JSON, it is not intended for public use. export function decodeResult(json: unknown): TaskingSearchResult|null { - let polygons: number[][][][] = []; - const areas: taskingArea = {target: 0, scene: 0}; + let polygon: number[][][] = []; + const metrics: taskingMetrics = {windowsAvailable: 0, windowsRequired: 0, orderArea: 0, moq: 0,}; let startDate = new Date(), endDate = new Date(); let supplier = "", orderingID = ""; let offNadir = 0, gsd = 0; const bands: Band[] = []; const bundles: BundleOption[] = []; const licenses: License[] = []; + const priorities: Priority[] = []; + const clouds: CloudLevel[] = []; const platforms: string[] = []; const annotations: annotation[] = []; @@ -206,25 +227,31 @@ export function decodeResult(json: unknown): TaskingSearchResult|null { } }); } - // areas - if (argMap?.areas && typeof argMap.areas == "object") { - const innerMap = argMap.areas as {[key: string]: unknown} - if (innerMap?.target && typeof innerMap.target == "number") { - areas.target = innerMap.target; + // metrics + if (argMap?.metrics && typeof argMap.metrics == "object") { + const innerMap = argMap.metrics as {[key: string]: unknown}; + if (innerMap?.windowsAvailable && typeof innerMap.windowsAvailable == "number") { + metrics.windowsAvailable = innerMap.windowsAvailable; + } + if (innerMap?.windowsRequired && typeof innerMap.windowsRequired == "number") { + metrics.windowsRequired = innerMap.windowsRequired; + } + if (innerMap?.orderArea && typeof innerMap.orderArea == "number") { + metrics.orderArea = innerMap.orderArea; } - if (innerMap?.scene && typeof innerMap.scene == "number") { - areas.scene = innerMap.scene; + if (innerMap?.moq && typeof innerMap.moq == "number") { + metrics.moq = innerMap.moq; } } else { return null; } // polygons if (argMap?.polygons && Array.isArray(argMap.polygons)) { - const p = decodeMultiPolygon(argMap.polygons); + const p = decodePolygon(argMap.polygons); if (!p) { return null; } - polygons = p; + polygon = p; } else { return null; } @@ -256,6 +283,28 @@ export function decodeResult(json: unknown): TaskingSearchResult|null { } }); } + // priorities + if (argMap?.priorities && Array.isArray(argMap.priorities)) { + argMap.priorities.forEach((b) => { + const li = decodePriority(b); + if (li) { + priorities.push(li); + } else { + return null; + } + }); + } + // cloud levels + if (argMap?.cloud && Array.isArray(argMap.cloud)) { + argMap.cloud.forEach((b) => { + const li = decodeCloudLevel(b); + if (li) { + clouds.push(li); + } else { + return null; + } + }); + } // annotations if (argMap?.annotations && Array.isArray(argMap.annotations)) { argMap.annotations.forEach((a) => { @@ -281,10 +330,10 @@ export function decodeResult(json: unknown): TaskingSearchResult|null { return new TaskingSearchResult( - polygons, - areas, + polygon, startDate, endDate, + metrics, gsd, supplier, orderingID, @@ -292,6 +341,8 @@ export function decodeResult(json: unknown): TaskingSearchResult|null { bands, bundles, licenses, + priorities, + clouds, platforms, annotations, ); From 34cdbd93f1c1c3f285f684b38691ffde25c7b332 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 00:06:10 +1000 Subject: [PATCH 15/30] moving order structure back into archive --- src/archive/batch-order.ts | 131 ++++++++++++++++++++++++- src/archive/index.ts | 4 +- src/archive/order-request.ts | 182 ++++++++++++++++++++++++++++++++++- src/orders/batch-order.ts | 130 ------------------------- src/orders/order-request.ts | 181 ---------------------------------- 5 files changed, 311 insertions(+), 317 deletions(-) delete mode 100644 src/orders/batch-order.ts delete mode 100644 src/orders/order-request.ts diff --git a/src/archive/batch-order.ts b/src/archive/batch-order.ts index 7f9f731..3aa5609 100644 --- a/src/archive/batch-order.ts +++ b/src/archive/batch-order.ts @@ -1,3 +1,130 @@ -import BatchOrderRequest from "../orders/batch-order"; +import OrderRequest, { orderRequest } from "./order-request"; -export default BatchOrderRequest; +export default class BatchOrderRequest { + private _orders: OrderRequest[] = []; + private _webhooks: string[] = []; + private _emails: string[] = []; + private _team?: string; + private _coupon?: string; + private _payment?: string; + + /** + * sets the list of order requests to be submitted + * @param {OrderRequest[]} orders the list of order requests + */ + setOrders(orders: OrderRequest[]): void { + this._orders = orders; + } + + /** + * Add an order request to the list to be placed as a batch + * @param {OrderRequest} order the order to be added + */ + addOrder(order: OrderRequest): void { + this._orders.push(order); + } + + /** + * sets the list of webhooks the order will notify + * @param {string[]} hooks The list of webhooks + */ + setWebhooks(hooks: string[]): void { + this._webhooks = hooks; + } + + /** + * Add a webhook to the list to notify for the order + * @param {string} hook the webhook to notify + */ + addWebhook(hook: string): void { + if (!this._webhooks) { + this._webhooks = []; + } + this._webhooks.push(hook); + } + + /** + * sets the list of emails the order will notify + * @param {string[]} emails The list of emails + */ + setEmails(emails: string[]): void { + this._emails = emails; + } + + /** + * Add a email to the list to notify for the order + * @param {string} email the email to notify + */ + addEmail(email: string): void { + if (!this._emails) { + this._emails = []; + } + this._emails.push(email); + } + + /** + * Set the team the orders will be made available to, if unset, will use the accounts default team + * @param {string} teamID the uuid of the team to share data with + */ + setTeamSharing(teamID?: string): void { + this._team = teamID; + } + + /** + * Set the coupon code which may discount the set of orders + * @param {string} coupon the coupon key to apply to the orders + */ + setCouponCode(coupon?: string): void { + this._coupon = coupon; + } + + /** + * Set the payment account these orders will be billed to (if applicable). + * This will supercede any billing accounts set on individual orders. + * If unset, will use the accounts default billing account. + * @param {string} accountID the uuid of the billing account to charge + */ + setPaymentAccount(accountID?: string): void { + this._payment = accountID; + } + + valid(): boolean { + if (this._orders.length < 1) { + return false; + } + + const valid = this._orders.map((o) => {return o.valid()}).reduce((p, c) => {return p&&c}, true); + if (!valid) { + return valid; + } + + return true; + } + _toJSON(stringify: boolean): string|batchOrderRequest { + if (!this.valid()) { + return ""; + } + const res: batchOrderRequest = { + orders: this._orders.map((o)=>{return o._toJSON(false) as orderRequest}), + webhooks: this._webhooks, + emails: this._emails, + team: this._team, + coupon: this._coupon, + payment: this._payment, + }; + + if (stringify) { + return JSON.stringify(res); + } + return res; + } +} + +export interface batchOrderRequest { + orders: orderRequest[]; + webhooks: string[]; + emails: string[]; + team?: string; + coupon?: string; + payment?: string; +} diff --git a/src/archive/index.ts b/src/archive/index.ts index 09766dc..528c8fc 100644 --- a/src/archive/index.ts +++ b/src/archive/index.ts @@ -1,10 +1,10 @@ import SearchRequest from "./search-request"; import SearchResponse, { decodeResponse, decodeResultSet } from "./search/response"; import Order, { fromJSON as orderFromJSON } from "../orders/order"; -import OrderRequest from "../orders/order-request"; +import OrderRequest from "./order-request"; import paths from "../util/paths"; import { jsonOrError, requestBuilder } from "../util/request"; -import BatchOrderRequest from "../orders/batch-order"; +import BatchOrderRequest from "./batch-order"; /** * @class Archive wraps the API requests to the archive imagery API diff --git a/src/archive/order-request.ts b/src/archive/order-request.ts index 2fffc3f..f21f83d 100644 --- a/src/archive/order-request.ts +++ b/src/archive/order-request.ts @@ -1,3 +1,181 @@ -import OrderRequest from "../orders/order-request"; +import SearchResult from "./search/result"; -export default OrderRequest; +/** + * @class OrderRequest wraps the details of an order request + * + * @see {https://arlula.com/documentation/#archive-order|Archive Order endpoint documentation} + * or + * @see {https://arlula.com/documentation/#ref-order-request|Archive order request structure reference} + */ +export default class OrderRequest { + private _req?: SearchResult; + private _id: string; + private _eula: string; + private _bundleKey: string; + private _webhooks?: string[]; + private _emails?: string[]; + private _team?: string; + private _coupon?: string; + private _payment?: string; + + /** + * creates a new order request + * @param {string|SearchResult} searchID The search result (scene) to order, or its corresponding ID + * @param {string} eula The EULA for the order to confirm acceptance + * @param {string} bundleKey the bundle of imagery bands and processing to order + * @param {string[]} [webhooks] Any webhooks to notify of the orders status + * @param {string[]} [emails] Any emails to notify of the orders status + */ + constructor(searchID: string|SearchResult, eula: string, bundleKey: string, webhooks?: string[], emails?: string[]) { + if (typeof searchID === "string") { + this._id = searchID + } else { + this._id = searchID.orderingID; + this._req = searchID; + } + + this._eula = eula; + this._bundleKey = bundleKey; + + if (webhooks) { + this._webhooks = webhooks; + } + if (emails) { + this._emails = emails; + } + } + + /** + * sets the list of webhooks the order will notify + * @param {string[]} hooks The list of webhooks + */ + setWebhooks(hooks: string[]): void { + this._webhooks = hooks; + } + + /** + * Add a webhook to the list to notify for the order + * @param {string} hook the webhook to notify + */ + addWebhook(hook: string): void { + if (!this._webhooks) { + this._webhooks = []; + } + this._webhooks.push(hook); + } + + /** + * sets the list of emails the order will notify + * @param {string[]} emails The list of emails + */ + setEmails(emails: string[]): void { + this._emails = emails; + } + + /** + * Add a email to the list to notify for the order + * @param {string} email the email to notify + */ + addEmail(email: string): void { + if (!this._emails) { + this._emails = []; + } + this._emails.push(email); + } + + /** + * Set the team the order will be made available to, if unset, will use the accounts default team + * @param {string} teamID the uuid of the team to share data with + */ + setTeamSharing(teamID?: string): void { + this._team = teamID; + } + + /** + * Set the coupon code which may discount the order + * @param {string} coupon the coupon key to apply to the order + */ + setCouponCode(coupon?: string): void { + this._coupon = coupon; + } + + /** + * Set the payment account this order will be billed to (if applicable). + * If unset, will use the accounts default billing account. + * @param {string} accountID the uuid of the billing account to charge + */ + setPaymentAccount(accountID?: string): void { + this._payment = accountID; + } + + /** + * Checks if the order request is valid or requires additional details/details don't match + * @returns {boolean} whether the order request is valid + */ + valid(): boolean { + + if (!this._id) { + return false; + } + + if (!this._eula) { + return false; + } + + let found = false; + this._req?.licenses.some((v) => { + if (v.href === this._eula) { + found = true; + return found; + } + }) + if (this._req && !found) { + return false; + } + + if (this._bundleKey == "") { + return false; + } + + return true; + } + + /** + * Converts the request to its JSON ready form + * + * Note: this is for internal use and is not intended for use by end users + * + * @param {boolean} stringify determines whether the JSON should be marshalled to a string, or returned as an object + */ + _toJSON(stringify: boolean): string|orderRequest { + if (!this.valid()) { + return ""; + } + const res: orderRequest = { + id: this._id, + eula: this._eula, + bundleKey: this._bundleKey, + webhooks: this._webhooks, + emails: this._emails, + team: this._team, + coupon: this._coupon, + payment: this._payment, + }; + + if (stringify) { + return JSON.stringify(res); + } + return res; + } +} + +export interface orderRequest { + id: string; + eula: string; + bundleKey: string; + webhooks?: string[]; + emails?: string[]; + team?: string; + coupon?: string; + payment?: string; +} diff --git a/src/orders/batch-order.ts b/src/orders/batch-order.ts deleted file mode 100644 index 3aa5609..0000000 --- a/src/orders/batch-order.ts +++ /dev/null @@ -1,130 +0,0 @@ -import OrderRequest, { orderRequest } from "./order-request"; - -export default class BatchOrderRequest { - private _orders: OrderRequest[] = []; - private _webhooks: string[] = []; - private _emails: string[] = []; - private _team?: string; - private _coupon?: string; - private _payment?: string; - - /** - * sets the list of order requests to be submitted - * @param {OrderRequest[]} orders the list of order requests - */ - setOrders(orders: OrderRequest[]): void { - this._orders = orders; - } - - /** - * Add an order request to the list to be placed as a batch - * @param {OrderRequest} order the order to be added - */ - addOrder(order: OrderRequest): void { - this._orders.push(order); - } - - /** - * sets the list of webhooks the order will notify - * @param {string[]} hooks The list of webhooks - */ - setWebhooks(hooks: string[]): void { - this._webhooks = hooks; - } - - /** - * Add a webhook to the list to notify for the order - * @param {string} hook the webhook to notify - */ - addWebhook(hook: string): void { - if (!this._webhooks) { - this._webhooks = []; - } - this._webhooks.push(hook); - } - - /** - * sets the list of emails the order will notify - * @param {string[]} emails The list of emails - */ - setEmails(emails: string[]): void { - this._emails = emails; - } - - /** - * Add a email to the list to notify for the order - * @param {string} email the email to notify - */ - addEmail(email: string): void { - if (!this._emails) { - this._emails = []; - } - this._emails.push(email); - } - - /** - * Set the team the orders will be made available to, if unset, will use the accounts default team - * @param {string} teamID the uuid of the team to share data with - */ - setTeamSharing(teamID?: string): void { - this._team = teamID; - } - - /** - * Set the coupon code which may discount the set of orders - * @param {string} coupon the coupon key to apply to the orders - */ - setCouponCode(coupon?: string): void { - this._coupon = coupon; - } - - /** - * Set the payment account these orders will be billed to (if applicable). - * This will supercede any billing accounts set on individual orders. - * If unset, will use the accounts default billing account. - * @param {string} accountID the uuid of the billing account to charge - */ - setPaymentAccount(accountID?: string): void { - this._payment = accountID; - } - - valid(): boolean { - if (this._orders.length < 1) { - return false; - } - - const valid = this._orders.map((o) => {return o.valid()}).reduce((p, c) => {return p&&c}, true); - if (!valid) { - return valid; - } - - return true; - } - _toJSON(stringify: boolean): string|batchOrderRequest { - if (!this.valid()) { - return ""; - } - const res: batchOrderRequest = { - orders: this._orders.map((o)=>{return o._toJSON(false) as orderRequest}), - webhooks: this._webhooks, - emails: this._emails, - team: this._team, - coupon: this._coupon, - payment: this._payment, - }; - - if (stringify) { - return JSON.stringify(res); - } - return res; - } -} - -export interface batchOrderRequest { - orders: orderRequest[]; - webhooks: string[]; - emails: string[]; - team?: string; - coupon?: string; - payment?: string; -} diff --git a/src/orders/order-request.ts b/src/orders/order-request.ts deleted file mode 100644 index 63551dd..0000000 --- a/src/orders/order-request.ts +++ /dev/null @@ -1,181 +0,0 @@ -import SearchResult from "../archive/search/result"; - -/** - * @class OrderRequest wraps the details of an order request - * - * @see {https://arlula.com/documentation/#archive-order|Archive Order endpoint documentation} - * or - * @see {https://arlula.com/documentation/#ref-order-request|Archive order request structure reference} - */ -export default class OrderRequest { - private _req?: SearchResult; - private _id: string; - private _eula: string; - private _bundleKey: string; - private _webhooks?: string[]; - private _emails?: string[]; - private _team?: string; - private _coupon?: string; - private _payment?: string; - - /** - * creates a new order request - * @param {string|SearchResult} searchID The search result (scene) to order, or its corresponding ID - * @param {string} eula The EULA for the order to confirm acceptance - * @param {string} bundleKey the bundle of imagery bands and processing to order - * @param {string[]} [webhooks] Any webhooks to notify of the orders status - * @param {string[]} [emails] Any emails to notify of the orders status - */ - constructor(searchID: string|SearchResult, eula: string, bundleKey: string, webhooks?: string[], emails?: string[]) { - if (typeof searchID === "string") { - this._id = searchID - } else { - this._id = searchID.orderingID; - this._req = searchID; - } - - this._eula = eula; - this._bundleKey = bundleKey; - - if (webhooks) { - this._webhooks = webhooks; - } - if (emails) { - this._emails = emails; - } - } - - /** - * sets the list of webhooks the order will notify - * @param {string[]} hooks The list of webhooks - */ - setWebhooks(hooks: string[]): void { - this._webhooks = hooks; - } - - /** - * Add a webhook to the list to notify for the order - * @param {string} hook the webhook to notify - */ - addWebhook(hook: string): void { - if (!this._webhooks) { - this._webhooks = []; - } - this._webhooks.push(hook); - } - - /** - * sets the list of emails the order will notify - * @param {string[]} emails The list of emails - */ - setEmails(emails: string[]): void { - this._emails = emails; - } - - /** - * Add a email to the list to notify for the order - * @param {string} email the email to notify - */ - addEmail(email: string): void { - if (!this._emails) { - this._emails = []; - } - this._emails.push(email); - } - - /** - * Set the team the order will be made available to, if unset, will use the accounts default team - * @param {string} teamID the uuid of the team to share data with - */ - setTeamSharing(teamID?: string): void { - this._team = teamID; - } - - /** - * Set the coupon code which may discount the order - * @param {string} coupon the coupon key to apply to the order - */ - setCouponCode(coupon?: string): void { - this._coupon = coupon; - } - - /** - * Set the payment account this order will be billed to (if applicable). - * If unset, will use the accounts default billing account. - * @param {string} accountID the uuid of the billing account to charge - */ - setPaymentAccount(accountID?: string): void { - this._payment = accountID; - } - - /** - * Checks if the order request is valid or requires additional details/details don't match - * @returns {boolean} whether the order request is valid - */ - valid(): boolean { - - if (!this._id) { - return false; - } - - if (!this._eula) { - return false; - } - - let found = false; - this._req?.licenses.some((v) => { - if (v.href === this._eula) { - found = true; - return found; - } - }) - if (this._req && !found) { - return false; - } - - if (this._bundleKey == "") { - return false; - } - - return true; - } - - /** - * Converts the request to its JSON ready form - * - * Note: this is for internal use and is not intended for use by end users - * - * @param {boolean} stringify determines whether the JSON should be marshalled to a string, or returned as an object - */ - _toJSON(stringify: boolean): string|orderRequest { - if (!this.valid()) { - return ""; - } - const res: orderRequest = { - id: this._id, - eula: this._eula, - bundleKey: this._bundleKey, - webhooks: this._webhooks, - emails: this._emails, - team: this._team, - coupon: this._coupon, - payment: this._payment, - }; - - if (stringify) { - return JSON.stringify(res); - } - return res; - } -} - -export interface orderRequest { - id: string; - eula: string; - bundleKey: string; - webhooks?: string[]; - emails?: string[]; - team?: string; - coupon?: string; - payment?: string; -} From c3d7894559853f8f76fc08a0184f5810aaf9b89f Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 00:06:56 +1000 Subject: [PATCH 16/30] implementing order structures for tasking in the tasking package --- src/tasking/batch-order.ts | 130 +++++++++++++++++++++++++++++++++++ src/tasking/index.ts | 4 +- src/tasking/order-request.ts | 42 +++++++++++ 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 src/tasking/batch-order.ts create mode 100644 src/tasking/order-request.ts diff --git a/src/tasking/batch-order.ts b/src/tasking/batch-order.ts new file mode 100644 index 0000000..3aa5609 --- /dev/null +++ b/src/tasking/batch-order.ts @@ -0,0 +1,130 @@ +import OrderRequest, { orderRequest } from "./order-request"; + +export default class BatchOrderRequest { + private _orders: OrderRequest[] = []; + private _webhooks: string[] = []; + private _emails: string[] = []; + private _team?: string; + private _coupon?: string; + private _payment?: string; + + /** + * sets the list of order requests to be submitted + * @param {OrderRequest[]} orders the list of order requests + */ + setOrders(orders: OrderRequest[]): void { + this._orders = orders; + } + + /** + * Add an order request to the list to be placed as a batch + * @param {OrderRequest} order the order to be added + */ + addOrder(order: OrderRequest): void { + this._orders.push(order); + } + + /** + * sets the list of webhooks the order will notify + * @param {string[]} hooks The list of webhooks + */ + setWebhooks(hooks: string[]): void { + this._webhooks = hooks; + } + + /** + * Add a webhook to the list to notify for the order + * @param {string} hook the webhook to notify + */ + addWebhook(hook: string): void { + if (!this._webhooks) { + this._webhooks = []; + } + this._webhooks.push(hook); + } + + /** + * sets the list of emails the order will notify + * @param {string[]} emails The list of emails + */ + setEmails(emails: string[]): void { + this._emails = emails; + } + + /** + * Add a email to the list to notify for the order + * @param {string} email the email to notify + */ + addEmail(email: string): void { + if (!this._emails) { + this._emails = []; + } + this._emails.push(email); + } + + /** + * Set the team the orders will be made available to, if unset, will use the accounts default team + * @param {string} teamID the uuid of the team to share data with + */ + setTeamSharing(teamID?: string): void { + this._team = teamID; + } + + /** + * Set the coupon code which may discount the set of orders + * @param {string} coupon the coupon key to apply to the orders + */ + setCouponCode(coupon?: string): void { + this._coupon = coupon; + } + + /** + * Set the payment account these orders will be billed to (if applicable). + * This will supercede any billing accounts set on individual orders. + * If unset, will use the accounts default billing account. + * @param {string} accountID the uuid of the billing account to charge + */ + setPaymentAccount(accountID?: string): void { + this._payment = accountID; + } + + valid(): boolean { + if (this._orders.length < 1) { + return false; + } + + const valid = this._orders.map((o) => {return o.valid()}).reduce((p, c) => {return p&&c}, true); + if (!valid) { + return valid; + } + + return true; + } + _toJSON(stringify: boolean): string|batchOrderRequest { + if (!this.valid()) { + return ""; + } + const res: batchOrderRequest = { + orders: this._orders.map((o)=>{return o._toJSON(false) as orderRequest}), + webhooks: this._webhooks, + emails: this._emails, + team: this._team, + coupon: this._coupon, + payment: this._payment, + }; + + if (stringify) { + return JSON.stringify(res); + } + return res; + } +} + +export interface batchOrderRequest { + orders: orderRequest[]; + webhooks: string[]; + emails: string[]; + team?: string; + coupon?: string; + payment?: string; +} diff --git a/src/tasking/index.ts b/src/tasking/index.ts index dbb402d..1e08b03 100644 --- a/src/tasking/index.ts +++ b/src/tasking/index.ts @@ -4,8 +4,8 @@ import { jsonOrError, requestBuilder } from "../util/request"; import TaskingSearchRequest from "./search-request"; import { TaskingSearchResponse } from "./search-response"; import { decodeResponse } from "./search-response"; -import OrderRequest from "../orders/order-request"; -import BatchOrderRequest from "../orders/batch-order"; +import OrderRequest from "./order-request"; +import BatchOrderRequest from "./batch-order"; /** * @class Tasking wraps the API requests to the imagery tasking API diff --git a/src/tasking/order-request.ts b/src/tasking/order-request.ts new file mode 100644 index 0000000..53a8285 --- /dev/null +++ b/src/tasking/order-request.ts @@ -0,0 +1,42 @@ +import baseRequest, { orderRequest as baseOutput } from "../archive/order-request"; + +export default class OrderRequest extends baseRequest { + private _priority: string; + private _cloud = -1; + constructor(orderingID: string, eula: string, bundleKey: string, priority: string, cloud: number, webhooks?: string[], emails?: string[]) { + super(orderingID, eula, bundleKey, webhooks, emails); + this._priority = priority; + this._cloud = cloud; + } + + valid(): boolean { + if (!super.valid()) { + return false; + } + + if (this._priority == "") { + return false; + } + + if (this._cloud < 0) { + return false; + } + + return true; + } + _toJSON(stringify: boolean): string|orderRequest { + const req = super._toJSON(false) as orderRequest; + req.priority = this._priority; + req.cloud = this._cloud; + + if (stringify) { + return JSON.stringify(req); + } + return req; + } +} + +export interface orderRequest extends baseOutput { + priority: string; + cloud: number; +} \ No newline at end of file From a6e1b7e1ed5d9705c3b1580eddd7de0ca754de39 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 08:19:54 +1000 Subject: [PATCH 17/30] updating references in archive tests --- e2e/archive/batch-test.ts | 20 +++++++++++--------- e2e/archive/order-test.ts | 28 ++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/e2e/archive/batch-test.ts b/e2e/archive/batch-test.ts index db8803e..55a8fbe 100644 --- a/e2e/archive/batch-test.ts +++ b/e2e/archive/batch-test.ts @@ -1,7 +1,8 @@ import Arlula from "../../dist"; -import OrderRequest from "../../dist/orders/order-request"; -import BatchOrderRequest from "../../dist/orders/batch-order"; -import Order, { OrderStatus } from "../../dist/orders/order"; +import OrderRequest from "../../dist/archive/order-request"; +import BatchOrderRequest from "../../dist/archive/batch-order"; +import Order from "../../dist/orders/order"; +import { StatusCode } from "../../dist/orders/status"; const tests = [ test1, @@ -26,22 +27,23 @@ function test1(client: Arlula) { req.addOrder(new OrderRequest(process.env.order_key || "", process.env.order_eula || "", process.env.order_bundle || "default")); req.addOrder(new OrderRequest(process.env.order_key2 || "", process.env.order_eula || "", process.env.order_bundle || "default")); return client.archive().batchOrder(req) - .then((resp) => { - if (resp.length != 2) { + .then(async (resp) => { + const ds = await resp.datasets; + if (ds.length != 2) { console.error("archive batch 1 - Response to batch order does not match request length"); return Promise.reject("archive batch 1 - Response to batch order does not match request length"); } - for (let i=0; i { + .then(async (resp) => { if (!resp.id) { console.error("archive order 1 - Receives order without ID"); return Promise.reject("archive order 1 - Receives order without ID"); } // pre defined landsat order, will be complete and have resource results - if (resp.status !== OrderStatus.Complete) { + if (resp.status !== StatusCode.Complete) { console.error("archive order 1 - order not complete"); return Promise.reject("archive order 1 - order not complete"); } - if (!resp.resources) { + const ds = await resp.datasets; + if (!ds) { + console.error("archive order 1 - Landsat order with no datasets"); + return Promise.reject("archive order 1 - Landsat order with no datasets"); + } + if (!ds[0].resources) { console.error("archive order 1 - Landsat order with no resources"); return Promise.reject("archive order 1 - Landsat order with no resources"); } @@ -69,17 +75,23 @@ function test2(client: Arlula) { const req = new OrderRequest(process.env.order_key || "", process.env.order_eula || "", process.env.order_bundle || "default"); return client.archive().order(req) }) - .then((resp) => { + .then(async (resp) => { if (!resp.id) { console.error("archive order 2 - Receives order without ID"); return Promise.reject("archive order 2 - Receives order without ID"); } // pre defined landsat order, will be complete and have resource results - if (resp.status !== OrderStatus.Complete) { + if (resp.status !== StatusCode.Complete) { console.error("archive order 2 - order not complete"); return Promise.reject("archive order 2 - order not complete"); } - if (!resp.resources) { + const ds = await resp.datasets; + if (!ds) { + console.error("archive order 2 - Landsat order with no datasets"); + return Promise.reject("archive order 2 - Landsat order with no datasets"); + } + + if (!ds[0].resources) { console.error("archive order 2 - Landsat order with no resources"); return Promise.reject("archive order 2 - Landsat order with no resources"); } From b36ec68af78ebff77bcf428348a864b57ccee7db Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 08:31:18 +1000 Subject: [PATCH 18/30] updating tasking test references --- e2e/tasking/batch-test.ts | 40 +++++++++++++++++++++++++------------- e2e/tasking/order-test.ts | 32 ++++++++++++++++++++++-------- e2e/tasking/search-test.ts | 4 ++-- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/e2e/tasking/batch-test.ts b/e2e/tasking/batch-test.ts index b6ca4b7..4ef6f39 100644 --- a/e2e/tasking/batch-test.ts +++ b/e2e/tasking/batch-test.ts @@ -1,7 +1,8 @@ import Arlula from "../../dist"; -import OrderRequest from "../../dist/orders/order-request"; -import BatchOrderRequest from "../../dist/orders/batch-order"; -import Order, { OrderStatus } from "../../dist/orders/order"; +import OrderRequest from "../../dist/tasking/order-request"; +import BatchOrderRequest from "../../dist/tasking/batch-order"; +import Order from "../../dist/orders/order"; +import { StatusCode } from "../../dist/orders/status"; const tests = [ test1, @@ -19,27 +20,38 @@ export default function runBatchTests(client: Arlula): Promise { function test1(client: Arlula) { console.log("tasking batch 1"); const req = new BatchOrderRequest(); - req.addOrder(new OrderRequest(process.env.tasking_order_key || "", process.env.tasking_order_eula || "", process.env.tasking_order_bundle || "default")); - req.addOrder(new OrderRequest(process.env.tasking_order_key2 || "", process.env.tasking_order_eula || "", process.env.tasking_order_bundle || "default")); + req.addOrder(new OrderRequest(process.env.tasking_order_key || "", process.env.tasking_order_eula || "", process.env.tasking_order_bundle || "default", process.env.tasking_order_priority || "standard", parseInt(process.env.tasking_order_cloud || "70"))); + req.addOrder(new OrderRequest(process.env.tasking_order_key2 || "", process.env.tasking_order_eula || "", process.env.tasking_order_bundle || "default", process.env.tasking_order_priority || "standard", parseInt(process.env.tasking_order_cloud || "70"))); return client.tasking().batchOrder(req) - .then((resp) => { - if (resp.length != 2) { + .then(async (resp) => { + if (resp.status != StatusCode.Pending) { + console.error("tasking batch 1 - Response to batch order is not in the pending state"); + return Promise.reject("tasking batch 1 - Response to batch order is not in the pending state"); + } + + const campaigns = await resp.campaigns; + + if (campaigns.length != 2) { console.error("tasking batch 1 - Response to batch order does not match request length"); return Promise.reject("tasking batch 1 - Response to batch order does not match request length"); } - for (let i=0; i { // basic order function test1(client: Arlula) { console.log("tasking order 1"); - const req = new OrderRequest(process.env.tasking_order_key || "", process.env.tasking_order_eula || "", process.env.tasking_order_bundle || "default"); + const req = new OrderRequest(process.env.tasking_order_key || "", process.env.tasking_order_eula || "", process.env.tasking_order_bundle || "default", process.env.tasking_order_priority || "standard", parseInt(process.env.tasking_order_cloud || "70")); return client.tasking().order(req) - .then((resp) => { + .then(async (resp) => { if (!resp.id) { console.error("tasking order 1 - Receives order without ID"); return Promise.reject("tasking order 1 - Receives order without ID"); } // pre defined order, will be pending approval and not have resource results - if (resp.status !== OrderStatus.PendingApproval) { + if (resp.status !== StatusCode.PendingApproval) { console.error("tasking order 1 - order not pending approval: ", resp.status); return Promise.reject("tasking order 1 - order not pending approval"); } - if (resp.resources.length) { - console.error("tasking order 1 - test tasking order with resources"); - return Promise.reject("tasking order 1 - test tasking order with resources"); + + const cpgn = await resp.campaigns; + if (!cpgn.length) { + console.error("tasking order 1 - test tasking order with no campaigns"); + return Promise.reject("tasking order 1 - test tasking order with no campaigns"); + } + + if (cpgn[0].status != StatusCode.PendingApproval) { + console.error("tasking order 1 - test tasking order returned an incorrect initial status"); + return Promise.reject("tasking order 1 - test tasking order returned an incorrect initial status"); + } + if (cpgn[0].start <= (new Date())) { + console.error("tasking order 1 - test tasking order with start date in the past"); + return Promise.reject("tasking order 1 - test tasking order with start date in the past"); + } + if (cpgn[0].end <= (new Date())) { + console.error("tasking order 1 - test tasking order with end date in the past"); + return Promise.reject("tasking order 1 - test tasking order with end date in the past"); } }) .catch(exceptionHandler("tasking order 1")); diff --git a/e2e/tasking/search-test.ts b/e2e/tasking/search-test.ts index 7e96856..ed9dc37 100644 --- a/e2e/tasking/search-test.ts +++ b/e2e/tasking/search-test.ts @@ -64,12 +64,12 @@ function expectedError(label: string) { function testResult(prefix: string, r: TaskingSearchResult): string { // bounding - if (r.polygons.length == 0) { + if (r.polygon.length == 0) { console.error(prefix, " is not populated"); console.log(r); return "scene polygon is not populated"; } - if (!JSON.stringify(r.polygons).startsWith("[[[[")) { + if (!JSON.stringify(r.polygon).startsWith("[[[")) { console.error(prefix, " - scene polygon is not valid"); console.log(r); return "scene polygon is not valid"; From cfd5a4b255745fef68a6810e7becab665031af0c Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 08:41:58 +1000 Subject: [PATCH 19/30] cleanup order endpoint errors before adding new endpoints --- e2e/orders/get-tests.ts | 20 +++++++++++--------- e2e/orders/list-tests.ts | 30 +++++++++++++++++++++++++++--- e2e/orders/resource-test.ts | 2 +- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/e2e/orders/get-tests.ts b/e2e/orders/get-tests.ts index f6508c5..e0eb572 100644 --- a/e2e/orders/get-tests.ts +++ b/e2e/orders/get-tests.ts @@ -1,5 +1,7 @@ import Arlula from "../../dist/index"; -import Order, { OrderStatus } from "../../dist/orders/order"; +import Dataset from "../../dist/orders/dataset"; +import Order from "../../dist/orders/order"; +import { StatusCode } from "../../dist/orders/status"; const tests = [ test1, @@ -18,21 +20,21 @@ export default function runOrderGetTests(client: Arlula): Promise { // get from list function test1(client: Arlula) { console.log("order get 1") - return client.orders().list() + return client.orders().datasetList() .then((list) => { - if (!list.length) { + if (!list.length && list.content.length) { console.error("order get 1, failed to get list to check against"); return Promise.reject("order get 1, failed to get list to check against"); } - let sub: Order = list[0]; + let sub: Dataset = list.content[0]; for (let i=0; i { if (!order.id) { console.error("order get 2, got empty order"); @@ -83,7 +85,7 @@ function test3(client: Arlula) { console.log("order get 3") const tmpID = process.env.order_id || ""; const id = `${tmpID.substr(0,14)}6${tmpID.substr(15)}`; - return client.orders().get(id) + return client.orders().getDataset(id) .then((order) => { console.error("order get 3, got order from invalid ID: ", order); return Promise.reject("order get 3, got order from invalid ID: "+order); diff --git a/e2e/orders/list-tests.ts b/e2e/orders/list-tests.ts index 1699fea..951803e 100644 --- a/e2e/orders/list-tests.ts +++ b/e2e/orders/list-tests.ts @@ -1,15 +1,39 @@ import Arlula from "../../dist/index"; -export default function runOrderListTests(client: Arlula): Promise { + +const tests = [ + orderListTest, + // test1, + // test2, + // test3, + // test4, + // test5, + // test6, + // test7, + // test1Sort, + // testError1, + // testError2, + // testError3, +]; + +export default function runListTests(client: Arlula): Promise { + + return tests.reduce((p, test) => { + return p.then(() => test(client)); + }, Promise.resolve()); // initial + +} + +function orderListTest(client: Arlula) { console.log("order list") - return client.orders().list() + return client.orders().orderList() .then((ordList) => { if (!ordList.length) { console.log("Order list - list empty, expecting existing list"); return Promise.reject("order list - list empty, expecting existing list"); } for (let i=0; i // order get => child resource => download function test1(client: Arlula) { console.log("resource get 1"); - return client.orders().get(process.env.order_id || "") + return client.orders().getDataset(process.env.order_id || "") .then((order) => { if (!order.resources.length) { console.error("resource 1, Get order returned no resources"); From f165ee1dc738b9ad6858f6b8e24d9c6c4b9a3ee6 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 10:32:38 +1000 Subject: [PATCH 20/30] fixing type specifier in sub list debug message --- src/orders/order.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/orders/order.ts b/src/orders/order.ts index 2731842..bc27b30 100644 --- a/src/orders/order.ts +++ b/src/orders/order.ts @@ -140,7 +140,7 @@ export default class Order { .then(jsonOrError) .then((resp) => { if (!(resp instanceof Object)) { - return Promise.reject("Campaign dataset list endpoint returned a malformed response"); + return Promise.reject("Order campaign list endpoint returned a malformed response"); } const list = parseListResponse(this._client, resp as {[key: string]: unknown}, campaignFromJSON) @@ -163,7 +163,7 @@ export default class Order { .then(jsonOrError) .then((resp) => { if (!(resp instanceof Object)) { - return Promise.reject("Campaign dataset list endpoint returned a malformed response"); + return Promise.reject("Order dataset list endpoint returned a malformed response"); } const list = parseListResponse(this._client, resp as {[key: string]: unknown}, datasetFromJSON) From 762ed74ec0d6b142cf5d36feaaedf2a866e22355 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 15:57:20 +1000 Subject: [PATCH 21/30] adding missing manual order status --- src/orders/status.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/orders/status.ts b/src/orders/status.ts index d51d378..3991b09 100644 --- a/src/orders/status.ts +++ b/src/orders/status.ts @@ -17,7 +17,8 @@ export enum StatusCode { PostProcessing = "post-processing", Complete = "complete", // custom orders - Manual = "manual", + Manual = "manual", + PendingScreening = "pending-screen", // tasking orders PendingApproval = "pending-approval", Rejected = "rejected", From 658e2a40f20373900e73145fb2b58efc46abf4ce Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 15:58:01 +1000 Subject: [PATCH 22/30] handle custom datasets potentially not having a datetime --- src/orders/dataset.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/orders/dataset.ts b/src/orders/dataset.ts index 44be157..159c82a 100644 --- a/src/orders/dataset.ts +++ b/src/orders/dataset.ts @@ -76,14 +76,18 @@ export function fromJSON(client: requestBuilder, json: string|{[key: string]: un campaign = json.campaign; } if (!(json?.aoi)) { - return ""; + return "Dataset aoi missing"; } const polygon = decodePolygon(json.aoi) if (!polygon) { - return ""; + return "invalid dataset polygon"; } - if (typeof json.datetime !== "string") { - return "Dataset datetime missing"; + let date: Date|undefined + if (json.datetime) { + if (typeof json.datetime !== "string") { + return "Dataset datetime missing"; + } + date = new Date(json.datetime); } if (json.expiration && !(typeof json.expiration === "string" || json.expiration instanceof Date)) { return "Dataset Expiration invalid formatting"; @@ -119,7 +123,7 @@ export function fromJSON(client: requestBuilder, json: string|{[key: string]: un json.order, campaign, polygon, - new Date(json.datetime), + date, resources, json.expiration?new Date(json.expiration as string|Date):undefined, ); @@ -151,7 +155,7 @@ export default class Dataset { private _campaign: string; private _expiration?: Date; private _aoi: number[][][]; - private _datetime: Date; + private _datetime?: Date; private _resources: Resource[] = []; private detailed = false; /** @@ -197,7 +201,7 @@ export default class Dataset { order: string, campaign: string, aoi: number[][][], - datetime: Date, + datetime: Date|undefined, resources: Resource[], exp?: Date, ) { @@ -288,7 +292,7 @@ export default class Dataset { public get aoi(): number[][][] { return this._aoi; } - public get datetime(): Date { + public get datetime(): Date|undefined { return this._datetime; } From 19dc788e3fd6f7efaf0e81b0f4fd0328232ec6bc Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 15:58:20 +1000 Subject: [PATCH 23/30] dataset self load points to wrong endpoint --- src/orders/dataset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/orders/dataset.ts b/src/orders/dataset.ts index 159c82a..31080de 100644 --- a/src/orders/dataset.ts +++ b/src/orders/dataset.ts @@ -314,7 +314,7 @@ export default class Dataset { return Promise.resolve(this._resources); } - return this._client("GET", paths.OrderGet+"?id="+this._id) + return this._client("GET", paths.DatasetGet(this._id)) .then(jsonOrError) .then((resp) => { if (typeof resp !== "object") { From a495d7cd0ed9b168e20f8efd4204f24ba56bee63 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 17:09:13 +1000 Subject: [PATCH 24/30] fixing wrong field name in tasking response decode --- src/tasking/search-response.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tasking/search-response.ts b/src/tasking/search-response.ts index 97e3c63..963c1f0 100644 --- a/src/tasking/search-response.ts +++ b/src/tasking/search-response.ts @@ -245,9 +245,9 @@ export function decodeResult(json: unknown): TaskingSearchResult|null { } else { return null; } - // polygons - if (argMap?.polygons && Array.isArray(argMap.polygons)) { - const p = decodePolygon(argMap.polygons); + // polygon + if (argMap?.polygon && Array.isArray(argMap.polygon)) { + const p = decodePolygon(argMap.polygon); if (!p) { return null; } From 16ab6046832492c6aab41cb1a1d5d2e8d90874ab Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 17:22:24 +1000 Subject: [PATCH 25/30] updating environment variable references --- e2e/archive/order-test.ts | 14 +++++++------- e2e/collections/item-cud.ts | 10 +++++----- e2e/orders/resource-test.ts | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/e2e/archive/order-test.ts b/e2e/archive/order-test.ts index 9801f1e..6386bed 100644 --- a/e2e/archive/order-test.ts +++ b/e2e/archive/order-test.ts @@ -62,7 +62,7 @@ function test2(client: Arlula) { sceneLoop: for (let i=0; i { @@ -112,7 +112,7 @@ function testError1(client: Arlula) { console.error("archive order error 1 - unexpected error: ", e); return Promise.reject("archive order error 1 - "+e); } - if (!e.startsWith("Invalid ordering ID")) { + if (!e.startsWith("Invalid `id`")) { console.error("archive order error 1 - Unexpected error response: ", e) return Promise.reject("archive order error 1 - "+e); } @@ -129,7 +129,7 @@ function testError2(client: Arlula) { console.error("archive order error 2 - unexpected error: ", e); return Promise.reject("archive order error 2 - "+e); } - if (!e.startsWith("You must confirm acceptance of the EULA")) { + if (!e.startsWith("Invalid `eula`")) { console.error("archive order error 2 - Unexpected error response: ", e) return Promise.reject("archive order error 2 - "+e); } @@ -146,7 +146,7 @@ function testError3(client: Arlula) { console.error("archive order error 3 - unexpected error: ", e); return Promise.reject("archive order error 3 - "+e); } - if (!e.startsWith("Selected bundle is not an available option of this order")) { + if (!e.startsWith("Invalid `bundleKey`")) { console.error("archive order error 3 - Unexpected error response: ", e) return Promise.reject("archive order error 3 - "+e); } @@ -156,8 +156,8 @@ function testError3(client: Arlula) { function exceptionHandler(label: string) { return function (e: string) { - console.error("Error executing " + label + ": ", JSON.stringify(e)); - return Promise.reject(label+": "+JSON.stringify(e)); + console.error("Error executing " + label + ": ", e); + return Promise.reject(label+": "+e); } } diff --git a/e2e/collections/item-cud.ts b/e2e/collections/item-cud.ts index 0a1ebd7..39c78f2 100644 --- a/e2e/collections/item-cud.ts +++ b/e2e/collections/item-cud.ts @@ -15,11 +15,11 @@ export default function runItemCUDTests(client: Arlula): Promise { function test1ItemAdd(client: Arlula) { console.log("item-cud 1 - add"); - return client.collections().itemAdd(process.env.collection_id || "", process.env.order_id || "") + return client.collections().itemAdd(process.env.collection_id || "", process.env.dataset_id || "") .then(async () => { await new Promise((resolve, reject) => { setTimeout(() => { - client.collections().itemGet(process.env.collection_id || "", process.env.order_id || "") + client.collections().itemGet(process.env.collection_id || "", process.env.dataset_id || "") .then((resp) => { if (!resp) { console.log("collection item cud 1 - add: empty response"); @@ -29,7 +29,7 @@ function test1ItemAdd(client: Arlula) { console.log("collection item cud 1 - add: no id"); reject("collection item cud 1 - add: no id"); } - if (resp.id != process.env.order_id) { + if (resp.id != process.env.dataset_id) { console.log("collection item cud 1 - add: unexpected id"); reject("collection item cud 1 - add: unexpected id"); } @@ -44,12 +44,12 @@ function test1ItemAdd(client: Arlula) { function test2ItemRm(client: Arlula) { console.log("item-cud 2 - rm"); - return client.collections().itemAdd(process.env.collection_id || "", process.env.order_id || "") + return client.collections().itemAdd(process.env.collection_id || "", process.env.dataset_id || "") .then(async () => { // TODO: establish the cache duration before uncommenting this // await new Promise((resolve, reject) => { // setTimeout(() => { - // client.collections().itemGet(process.env.collection_id || "", process.env.order_id || "") + // client.collections().itemGet(process.env.collection_id || "", process.env.dataset_id || "") // .then((resp) => { // if (resp) { // console.log("collection item cud 2 - rm: response to removed item"); diff --git a/e2e/orders/resource-test.ts b/e2e/orders/resource-test.ts index 2fc44c3..99fc4af 100644 --- a/e2e/orders/resource-test.ts +++ b/e2e/orders/resource-test.ts @@ -19,7 +19,7 @@ export default function runOrderResourceTests(client: Arlula): Promise // order get => child resource => download function test1(client: Arlula) { console.log("resource get 1"); - return client.orders().getDataset(process.env.order_id || "") + return client.orders().getDataset(process.env.dataset_id || "") .then((order) => { if (!order.resources.length) { console.error("resource 1, Get order returned no resources"); From 1723648d4e3fad9e76ed0704d71a7c0faaadfcf1 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Wed, 14 Aug 2024 17:22:41 +1000 Subject: [PATCH 26/30] updating order api test suite for new endpoints --- e2e/orders/get-tests.ts | 138 ++++++++++++++++++++++++++++----------- e2e/orders/list-tests.ts | 112 ++++++++++++++++++++++++++++--- 2 files changed, 203 insertions(+), 47 deletions(-) diff --git a/e2e/orders/get-tests.ts b/e2e/orders/get-tests.ts index e0eb572..7966804 100644 --- a/e2e/orders/get-tests.ts +++ b/e2e/orders/get-tests.ts @@ -4,9 +4,11 @@ import Order from "../../dist/orders/order"; import { StatusCode } from "../../dist/orders/status"; const tests = [ - test1, - test2, - test3, + orderTest1, + campaignTest1, + datasetTest1, + datasetTest2, + datasetTest3, ]; export default function runOrderGetTests(client: Arlula): Promise { @@ -17,14 +19,76 @@ export default function runOrderGetTests(client: Arlula): Promise { } -// get from list -function test1(client: Arlula) { + +// =========== +// orders +// =========== + +// get direct +function orderTest1(client: Arlula) { console.log("order get 1") + return client.orders().getOrder(process.env.order_id || "") + .then(async (order) => { + if (!order.id) { + console.error("order get 1, got empty order"); + return Promise.reject("order get 2, got empty order"); + } + + try { + const c = await order.campaigns; + const d = await order.datasets; + if (!(c.length || d.length)) { + console.log("order get 1 - received order in list with no content campaigns or datasets"); + return Promise.reject("Order get 1 - received order in list with no content campaigns or datasets"); + } + } catch(e) { + return Promise.reject("Error accessing order sub resources: "+e) + } + }) + .catch((e) => { + console.error("order get 1, unexpected error getting order: ", e); + return Promise.reject("order get 1, unexpected error getting order: "+e) + }); +} + +// =========== +// campaigns +// =========== + +// get direct +function campaignTest1(client: Arlula) { + console.log("campaign get 1") + return client.orders().getCampaign(process.env.campaign_id || "") + .then(async (campaign) => { + if (!campaign.id) { + console.error("campaign get 1, got empty campaign"); + return Promise.reject("campaign get 2, got empty campaign"); + } + + const d = await campaign.datasets; + if (campaign.status == StatusCode.Complete && !d.length) { + console.log("Campaign get 1 - received campaign with no content datasets"); + return Promise.reject("Campaign get 1 - received campaign with no content datasets"); + } + }) + .catch((e) => { + console.error("campaign get 1, unexpected error getting campaign: ", e); + return Promise.reject("campaign get 1, unexpected error getting campaign: "+e) + }); +} + +// =========== +// datasets +// =========== + +// get from list +function datasetTest1(client: Arlula) { + console.log("dataset get 1") return client.orders().datasetList() .then((list) => { if (!list.length && list.content.length) { - console.error("order get 1, failed to get list to check against"); - return Promise.reject("order get 1, failed to get list to check against"); + console.error("dataset get 1, failed to get list to check against"); + return Promise.reject("dataset get 1, failed to get list to check against"); } let sub: Dataset = list.content[0]; for (let i=0; i { if (!res.length) { - console.error("order get 1, get order resources returned empty array"); - return Promise.reject("order get 1, get order resources returned empty array"); + console.error("dataset get 1, get dataset resources returned empty array"); + return Promise.reject("dataset get 1, get dataset resources returned empty array"); } }) .catch((e) => { - console.error("order get 1, error getting order: ", e); - return Promise.reject("order get 1: "+e); + console.error("dataset get 1, error getting dataset: ", e); + return Promise.reject("dataset get 1: "+e); }); }) .catch((e) => { - console.error("order get 1, unexpected error getting order list: ", e); - return Promise.reject("order get 1: "+e) + console.error("dataset get 1, unexpected error getting dataset: ", e); + return Promise.reject("dataset get 1: "+e) }); } // get direct -function test2(client: Arlula) { - console.log("order get 2") - return client.orders().getDataset(process.env.order_id || "") - .then((order) => { - if (!order.id) { - console.error("order get 2, got empty order"); - return Promise.reject("order get 2, got empty order"); +function datasetTest2(client: Arlula) { + console.log("dataset get 2") + return client.orders().getDataset(process.env.dataset_id || "") + .then((dataset) => { + if (!dataset.id) { + console.error("dataset get 2, got empty dataset"); + return Promise.reject("dataset get 2, got empty dataset"); } - if (!order.resources.length) { - console.error("order get 2, get order did not return resources"); - return Promise.reject("order get 2, get order did not return resources"); + if (!dataset.resources.length) { + console.error("dataset get 2, get dataset did not return resources"); + return Promise.reject("dataset get 2, get dataset did not return resources"); } }) .catch((e) => { - console.error("order get 2, unexpected error getting order: ", e); - return Promise.reject("order get 2, unexpected error getting order: "+e) + console.error("dataset get 2, unexpected error getting dataset: ", e); + return Promise.reject("dataset get 2, unexpected error getting dataset: "+e) }); } // get invalid ID // changing version to 6 (invalid version) to invalidate ID -function test3(client: Arlula) { - console.log("order get 3") - const tmpID = process.env.order_id || ""; +function datasetTest3(client: Arlula) { + console.log("dataset get 3") + const tmpID = process.env.dataset_id || ""; const id = `${tmpID.substr(0,14)}6${tmpID.substr(15)}`; return client.orders().getDataset(id) - .then((order) => { - console.error("order get 3, got order from invalid ID: ", order); - return Promise.reject("order get 3, got order from invalid ID: "+order); + .then((dataset) => { + console.error("dataset get 3, got dataset from invalid ID: ", dataset); + return Promise.reject("dataset get 3, got dataset from invalid ID: "+dataset); }) .catch((e) => { if (typeof e !== "string") { - console.error("order get 3, unexpected error object: ", e); + console.error("dataset get 3, unexpected error object: ", e); } - if (e.startsWith("No permission to order")) { + if (e.startsWith("No permission to dataset")) { // success case return; } - console.error("order get 3, unexpected error: ", e); - return Promise.reject("order get 3: "+e); + console.error("dataset get 3, unexpected error: ", e); + return Promise.reject("dataset get 3: "+e); }); } diff --git a/e2e/orders/list-tests.ts b/e2e/orders/list-tests.ts index 951803e..10b99c6 100644 --- a/e2e/orders/list-tests.ts +++ b/e2e/orders/list-tests.ts @@ -1,8 +1,11 @@ import Arlula from "../../dist/index"; +import { StatusCode } from "../../dist/orders/status"; const tests = [ orderListTest, + campaignListTest, + datasetListTest, // test1, // test2, // test3, @@ -25,24 +28,113 @@ export default function runListTests(client: Arlula): Promise { } function orderListTest(client: Arlula) { - console.log("order list") + console.log("Order list") return client.orders().orderList() - .then((ordList) => { - if (!ordList.length) { + .then(async (ordList) => { + if (ordList.page != 0) { + console.log("Order list - base list with non-zero page"); + return Promise.reject("Order list - base list with non-zero page"); + } + if (ordList.length != 20) { + console.log("Order list - base page without page length specified"); + return Promise.reject("Order list - base page without page length specified"); + } + if (!ordList.count || !ordList.content.length) { console.log("Order list - list empty, expecting existing list"); - return Promise.reject("order list - list empty, expecting existing list"); + return Promise.reject("Order list - list empty, expecting existing list"); } - for (let i=0; i { + console.error("Order list - unexpected error getting order list: ", e); + return Promise.reject("Order list - "+e); + }); + +} + +function campaignListTest(client: Arlula) { + console.log("Campaign list") + return client.orders().campaignList() + .then(async (campaignList) => { + if (campaignList.page != 0) { + console.log("Campaign list - base list with non-zero page"); + return Promise.reject("Campaign list - base list with non-zero page"); + } + if (campaignList.length != 20) { + console.log("Campaign list - base page without page length specified"); + return Promise.reject("Campaign list - base page without page length specified"); + } + if (!campaignList.count || !campaignList.content.length) { + console.log("Campaign list - list empty, expecting existing list"); + return Promise.reject("Campaign list - list empty, expecting existing list"); + } + for (let i=0; i { - console.error("order list - unexpected error getting order list: ", e); - return Promise.reject("order list - "+e); + console.error("Campaign list - unexpected error getting campaign list: ", e); + return Promise.reject("Campaign list - "+e); }); -} \ No newline at end of file +} + +function datasetListTest(client: Arlula) { + console.log("Dataset list") + return client.orders().datasetList() + .then(async (datasetList) => { + if (datasetList.page != 0) { + console.log("Dataset list - base list with non-zero page"); + return Promise.reject("Dataset list - base list with non-zero page"); + } + if (datasetList.length != 20) { + console.log("Dataset list - base page without page length specified"); + return Promise.reject("Dataset list - base page without page length specified"); + } + if (!datasetList.count || !datasetList.content.length) { + console.log("Dataset list - list empty, expecting existing list"); + return Promise.reject("Dataset list - list empty, expecting existing list"); + } + for (let i=0; i { + console.error("Dataset list - unexpected error getting dataset list: ", e); + return Promise.reject("Dataset list - "+e); + }); + +} From d1802b6a5bc57d48c984130362e1fa9383a51200 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Thu, 15 Aug 2024 10:25:32 +1000 Subject: [PATCH 27/30] fixing field name in priority encoding --- src/tasking/order-request.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasking/order-request.ts b/src/tasking/order-request.ts index 53a8285..f049988 100644 --- a/src/tasking/order-request.ts +++ b/src/tasking/order-request.ts @@ -26,7 +26,7 @@ export default class OrderRequest extends baseRequest { } _toJSON(stringify: boolean): string|orderRequest { const req = super._toJSON(false) as orderRequest; - req.priority = this._priority; + req.priorityKey = this._priority; req.cloud = this._cloud; if (stringify) { @@ -37,6 +37,6 @@ export default class OrderRequest extends baseRequest { } export interface orderRequest extends baseOutput { - priority: string; + priorityKey: string; cloud: number; } \ No newline at end of file From 6f17760ecf78242bb02be9f1f6e27af76a103948 Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Thu, 15 Aug 2024 10:25:46 +1000 Subject: [PATCH 28/30] fixing status code checking of tasking orders --- e2e/tasking/batch-test.ts | 6 +++--- e2e/tasking/order-test.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/e2e/tasking/batch-test.ts b/e2e/tasking/batch-test.ts index 4ef6f39..c17923c 100644 --- a/e2e/tasking/batch-test.ts +++ b/e2e/tasking/batch-test.ts @@ -24,9 +24,9 @@ function test1(client: Arlula) { req.addOrder(new OrderRequest(process.env.tasking_order_key2 || "", process.env.tasking_order_eula || "", process.env.tasking_order_bundle || "default", process.env.tasking_order_priority || "standard", parseInt(process.env.tasking_order_cloud || "70"))); return client.tasking().batchOrder(req) .then(async (resp) => { - if (resp.status != StatusCode.Pending) { - console.error("tasking batch 1 - Response to batch order is not in the pending state"); - return Promise.reject("tasking batch 1 - Response to batch order is not in the pending state"); + if (resp.status === StatusCode.Complete) { + console.error("tasking batch 1 - Response to batch order is complete when a tasking order"); + return Promise.reject("tasking batch 1 - Response to batch order is complete when a tasking order"); } const campaigns = await resp.campaigns; diff --git a/e2e/tasking/order-test.ts b/e2e/tasking/order-test.ts index e36ef9c..a8f5731 100644 --- a/e2e/tasking/order-test.ts +++ b/e2e/tasking/order-test.ts @@ -18,7 +18,7 @@ export default function runOrderTests(client: Arlula): Promise { // basic order function test1(client: Arlula) { console.log("tasking order 1"); - const req = new OrderRequest(process.env.tasking_order_key || "", process.env.tasking_order_eula || "", process.env.tasking_order_bundle || "default", process.env.tasking_order_priority || "standard", parseInt(process.env.tasking_order_cloud || "70")); + const req = new OrderRequest(process.env.tasking_order_key || "", process.env.tasking_order_eula || "", process.env.tasking_order_bundle || "default", process.env.tasking_order_priority || "standard", parseInt(process.env.tasking_order_cloud || "100")); return client.tasking().order(req) .then(async (resp) => { if (!resp.id) { @@ -26,9 +26,9 @@ function test1(client: Arlula) { return Promise.reject("tasking order 1 - Receives order without ID"); } // pre defined order, will be pending approval and not have resource results - if (resp.status !== StatusCode.PendingApproval) { - console.error("tasking order 1 - order not pending approval: ", resp.status); - return Promise.reject("tasking order 1 - order not pending approval"); + if (resp.status === StatusCode.Complete) { + console.error("tasking order 1 - order not pending fulfillment: ", resp.status); + return Promise.reject("tasking order 1 - order not pending fulfillment"); } const cpgn = await resp.campaigns; From 89aecb2b58f0bdd89d3666358027ae3eba1193fc Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Thu, 15 Aug 2024 10:33:07 +1000 Subject: [PATCH 29/30] adding new env config for test environment --- .github/workflows/npm-publish.yml | 2 ++ .github/workflows/test-pr.yml | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 6c19638..9af9949 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -25,6 +25,8 @@ jobs: test_tasking_order_key2: ${{ secrets.test_tasking_order_key2 }} test_tasking_order_eula: ${{ secrets.test_tasking_order_eula }} test_tasking_order_bundle: ${{ secrets.test_tasking_order_bundle }} + tasking_order_priority: ${{ secrets.test_tasking_order_priority }} + tasking_order_cloud: ${{ secrets.test_tasking_order_cloud }} build: needs: test runs-on: ubuntu-latest diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index b2410a9..2805bfc 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -41,6 +41,10 @@ on: required: true test_tasking_order_bundle: required: true + tasking_order_priority: + required: true + tasking_order_cloud: + required: true pull_request: types: [opened,edited,synchronize,reopened,ready_for_review,review_requested,] jobs: @@ -73,4 +77,6 @@ jobs: tasking_order_key: ${{ secrets.test_tasking_order_key }} tasking_order_key2: ${{ secrets.test_tasking_order_key2 }} tasking_order_eula: ${{ secrets.test_tasking_order_eula }} - tasking_order_bundle: ${{ secrets.test_tasking_order_bundle }} \ No newline at end of file + tasking_order_bundle: ${{ secrets.test_tasking_order_bundle }} + tasking_order_priority: ${{ secrets.test_tasking_order_priority }} + tasking_order_cloud: ${{ secrets.test_tasking_order_cloud }} \ No newline at end of file From 53611d39afb7b522df6624cd97936eb2beb9d84e Mon Sep 17 00:00:00 2001 From: Scott Owens Date: Thu, 15 Aug 2024 10:41:33 +1000 Subject: [PATCH 30/30] adding other missing secret values and fixing name-spacing of secrets --- .github/workflows/npm-publish.yml | 6 ++++-- .github/workflows/test-pr.yml | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 9af9949..076844a 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -15,6 +15,8 @@ jobs: test_order_bundle: ${{ secrets.test_order_bundle }} test_order_eula: ${{ secrets.test_order_eula }} test_order_id: ${{ secrets.test_order_id }} + test_campaign_id: ${{ secrets.test_campaign_id }} + test_dataset_id: ${{ secrets.test_dataset_id }} test_resource_id: ${{ secrets.test_resource_id }} test_resource_file1: ${{ secrets.test_resource_file1 }} test_resource_file2: ${{ secrets.test_resource_file2 }} @@ -25,8 +27,8 @@ jobs: test_tasking_order_key2: ${{ secrets.test_tasking_order_key2 }} test_tasking_order_eula: ${{ secrets.test_tasking_order_eula }} test_tasking_order_bundle: ${{ secrets.test_tasking_order_bundle }} - tasking_order_priority: ${{ secrets.test_tasking_order_priority }} - tasking_order_cloud: ${{ secrets.test_tasking_order_cloud }} + test_tasking_order_priority: ${{ secrets.test_tasking_order_priority }} + test_tasking_order_cloud: ${{ secrets.test_tasking_order_cloud }} build: needs: test runs-on: ubuntu-latest diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index 2805bfc..3a3058d 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -21,6 +21,10 @@ on: required: true test_order_id: required: true + test_campaign_id: + required: true + test_dataset_id: + required: true test_resource_id: required: true test_resource_file1: @@ -41,9 +45,9 @@ on: required: true test_tasking_order_bundle: required: true - tasking_order_priority: + test_tasking_order_priority: required: true - tasking_order_cloud: + test_tasking_order_cloud: required: true pull_request: types: [opened,edited,synchronize,reopened,ready_for_review,review_requested,] @@ -68,6 +72,8 @@ jobs: order_bundle: ${{ secrets.test_order_bundle }} order_eula: ${{ secrets.test_order_eula }} order_id: ${{ secrets.test_order_id }} + campaign_id: ${{ secrets.test_campaign_id }} + dataset_id: ${{ secrets.test_dataset_id }} resource_id: ${{ secrets.test_resource_id }} resource_file1: ${{ secrets.test_resource_file1 }} resource_file2: ${{ secrets.test_resource_file2 }}