@gooddata/api-client-bear
Version:
API Client for the GoodData platform
227 lines • 8.79 kB
JavaScript
// (C) 2007-2022 GoodData Corporation
import find from "lodash/find.js";
import omit from "lodash/omit.js";
import omitBy from "lodash/omitBy.js";
import isEmpty from "lodash/isEmpty.js";
import cloneDeep from "lodash/cloneDeep.js";
import { omitEmpty } from "./util.js";
const REQUEST_DEFAULTS = {
types: ["attribute", "metric", "fact"],
paging: {
offset: 0,
},
};
const LOAD_DATE_DATASET_DEFAULTS = {
includeUnavailableDateDataSetsCount: true,
includeAvailableDateAttributes: true,
};
/**
* Convert specific params in options to "requiredDataSets" structure. For more details look into
* res file https://github.com/gooddata/gdc-bear/blob/develop/resources/specification/internal/catalog.res
*
* @param options - Supported keys in options are:
* <ul>
* <li>dataSetIdentifier - in value is string identifier of dataSet - this leads to CUSTOM type
* <li>returnAllDateDataSets - true value means to return ALL values without dataSet differentiation
* <li>returnAllRelatedDateDataSets - only related date dataSets are loaded across all dataSets
* <li>by default we get PRODUCTION dataSets
* </ul>
* @returns "requiredDataSets" object hash.
*/
const getRequiredDataSets = (options = {}) => {
if (options.returnAllRelatedDateDataSets) {
return {};
}
if (options.returnAllDateDataSets) {
return { requiredDataSets: { type: "ALL" } };
}
if (options.dataSetIdentifier) {
return {
requiredDataSets: {
type: "CUSTOM",
customIdentifiers: [options.dataSetIdentifier],
},
};
}
return { requiredDataSets: { type: "PRODUCTION" } };
};
const buildItemDescriptionObjects = ({ columns, definitions }) => {
if (!columns) {
return [];
}
return columns.map((column) => {
const definition = find(definitions, ({ metricDefinition }) => metricDefinition.identifier === column);
const maql = definition?.metricDefinition?.expression;
if (maql) {
return { expression: maql };
}
return { uri: column };
});
};
const isStoredItemDescription = (itemDescription) => {
return !!itemDescription.uri;
};
const isAdHocItemDescription = (itemDescription) => {
return !!itemDescription.expression;
};
/**
* @internal
*/
export const unwrapItemDescriptionObject = (itemDescription) => {
if (isStoredItemDescription(itemDescription)) {
return itemDescription.uri;
}
if (isAdHocItemDescription(itemDescription)) {
return itemDescription.expression;
}
throw new Error("Item description can only have expression or uri");
};
// When the limit is more than 2000,
// catalog items endpoint returns status of 500
const CATALOG_ITEMS_LIMIT = 1000;
export class CatalogueModule {
xhr;
execution;
constructor(xhr, execution) {
this.xhr = xhr;
this.execution = execution;
}
/**
* Load all catalog items
* @param projectId - string
* @param options - ILoadCatalogItemsParams
*/
async loadAllItems(projectId, options = {}) {
const sanitizedOptions = omitEmpty(options);
const loadAll = async (requestOptions, items = []) => {
const result = await this.xhr.getParsed(`/gdc/internal/projects/${projectId}/catalog/items`, {
data: requestOptions,
});
const resultItems = result.catalogItems.items;
const updatedItems = [...items, ...resultItems];
if (resultItems.length === requestOptions.limit) {
const updatedRequestOptions = {
...requestOptions,
offset: result.catalogItems.paging.offset + requestOptions.limit,
};
return loadAll(updatedRequestOptions, updatedItems);
}
return updatedItems;
};
return loadAll({
offset: 0,
limit: CATALOG_ITEMS_LIMIT,
...sanitizedOptions,
});
}
/**
* Load catalog groups
* @param projectId - string
* @param options - ILoadCatalogGroupsParams
*/
async loadGroups(projectId, options = {}) {
const result = await this.xhr.getParsed(`/gdc/internal/projects/${projectId}/catalog/groups`, {
data: omitEmpty(options),
});
return result.catalogGroups;
}
/**
* Load available item uris by already used uris and expressions
* @param projectId - string
* @param options - ILoadAvailableCatalogItemsParams
*/
async loadAvailableItemUris(projectId, options) {
const sanitizedCatalogQueryRequest = omitBy(options.catalogQueryRequest, isEmpty);
const result = await this.xhr.postParsed(`/gdc/internal/projects/${projectId}/catalog/query`, {
data: {
catalogQueryRequest: sanitizedCatalogQueryRequest,
},
});
return result.catalogAvailableItems.items;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
loadItems(projectId, options = {}) {
const request = omit({
...REQUEST_DEFAULTS,
...options,
...getRequiredDataSets(options),
}, ["dataSetIdentifier", "returnAllDateDataSets", "attributesMap"]);
const mdObj = cloneDeep(options)?.bucketItems;
const attributesMap = options?.attributesMap;
const hasBuckets = mdObj?.buckets !== undefined;
if (hasBuckets) {
return this.loadItemDescriptionObjects(projectId, mdObj, attributesMap).then((descriptionObjects) => this.loadCatalog(projectId, {
...request,
bucketItems: descriptionObjects.map(unwrapItemDescriptionObject),
}));
}
return this.loadCatalog(projectId, request);
}
async loadDateDataSets(projectId, options) {
const mdObj = cloneDeep(options).bucketItems;
const descriptionObjects = mdObj
? await this.loadItemDescriptionObjects(projectId, mdObj, options.attributesMap)
: undefined;
const bucketItems = descriptionObjects?.map(unwrapItemDescriptionObject);
const omittedOptions = [
"filter",
"types",
"paging",
"dataSetIdentifier",
"returnAllDateDataSets",
"returnAllRelatedDateDataSets",
"attributesMap",
];
// includeObjectsWithTags has higher priority than excludeObjectsWithTags,
// so when present omit excludeObjectsWithTags
if (options.includeObjectsWithTags) {
omittedOptions.push("excludeObjectsWithTags");
}
const request = omit({
...LOAD_DATE_DATASET_DEFAULTS,
...REQUEST_DEFAULTS,
...options,
...getRequiredDataSets(options),
bucketItems,
}, omittedOptions);
return this.requestDateDataSets(projectId, request);
}
/**
* Loads item description objects and returns them
*
* @internal
*
* @param projectId - id of the project to load from
* @param mdObj - metadata object containing buckets, visualization class, properties etc.
* @param attributesMap - contains map of attributes where the keys are the attributes display forms URIs
* @param removeDateItems - whether to skip date items
* @returns ItemDescription which is either `{ uri: string }` or `{ expression: string }`
*/
async loadItemDescriptionObjects(projectId, mdObj, attributesMap = {}, removeDateItems = false) {
const definitionsAndColumns = await this.execution.mdToExecutionDefinitionsAndColumns(projectId, mdObj, { attributesMap, removeDateItems });
return buildItemDescriptionObjects(definitionsAndColumns);
}
/**
* Loads all available data sets.
* @param projectId - id of the project to load from
*/
async loadDataSets(projectId) {
const uri = `/gdc/dataload/internal/projects/${projectId}/csv/datasets`;
const response = await this.xhr.getParsed(uri);
return response.datasets.items;
}
requestDateDataSets(projectId, dateDataSetsRequest) {
const uri = `/gdc/internal/projects/${projectId}/loadDateDataSets`;
return this.xhr
.postParsed(uri, { data: { dateDataSetsRequest } })
.then((data) => data.dateDataSetsResponse);
}
loadCatalog(projectId, catalogRequest) {
const uri = `/gdc/internal/projects/${projectId}/loadCatalog`;
return this.xhr
.post(uri, { data: { catalogRequest } })
.then((r) => r.getData())
.then((data) => data.catalogResponse);
}
}
//# sourceMappingURL=catalogue.js.map