import { warn } from '@ember/debug'; import { ApiStatus } from 'wherehows-web/utils/api'; import { getJSON, putJSON } from 'wherehows-web/utils/api/fetcher'; import { datasetUrlById, datasetUrlByUrn } from 'wherehows-web/utils/api/datasets/shared'; import { IDatasetProperties, IDatasetPropertiesGetResponse, IDatasetPinotPropertiesGetResponse, IDatasetPinotProperties } from 'wherehows-web/typings/api/datasets/properties'; /** * Describes the interface for an element in the list generated by the buildPropertiesList function * @interface IPropertyItem */ interface IPropertyItem { isSelectController: boolean; key: keyof IDatasetProperties; value: string | Element; } /** * Constructs the dataset properties endpoint url based on the id * @param {number} id dataset id */ const datasetPropertiesUrlById = (id: number) => `${datasetUrlById(id)}/properties`; /** * Returns the url for a dataset deprecation endpoint by urn * @param {string} urn * @return {string} */ const datasetDeprecationUrlByUrn = (urn: string) => `${datasetUrlByUrn(urn)}/deprecate`; /** * Reads the response from the dataset properties endpoint and returns properties if found * @param {number} id the dataset id to get properties for * @returns {IDatasetPropertiesGetResponse | IDatasetPinotPropertiesGetResponse} */ const readDatasetProperties = async ( id: number ): Promise => { const { status, properties, message } = await getJSON({ url: datasetPropertiesUrlById(id) }); if (status === ApiStatus.OK && properties) { return properties; } // treat the error status with a record not found msg as empty set if (status === ApiStatus.ERROR && message === 'record not found') { return {}; } throw new Error('Exception occurred reading the dataset properties'); }; /** * Formats the property value as a date string * @param {keyof IDatasetProperties} property * @param {*} value * @returns {*} */ const formatPropertyDateValue = (property: keyof IDatasetProperties, value: any): any => { const isoStringDateProperties = ['modification_time', 'begin_date', 'lumos_process_time', 'end_date', 'oracle_time']; if (isoStringDateProperties.includes(property)) { if (+value < 0) { return value; } try { return new Date(value).toISOString(); } catch (e) { warn(`Property ${property} has an unexpected value ${value}`); throw e; } } if (property === 'dumpdate') { return [['-', 0, 4], ['-', 4, 6], [' ', 6, 8], [':', 8, 10], [':', 10, 12], ['', 12, 14]].reduce( (dateString, props: [string, number, number]): string => { const [postfix, start, end] = props; return value ? dateString + ('' + value).substring(start, end) + postfix : dateString; }, '' ); } return value; }; /** * Builds a list of IPropertyItem values * @param {IDatasetProperties} properties * @returns {Array} * @link IPropertyItem */ const buildPropertiesList = (properties: IDatasetProperties): Array => { return Object.keys(properties).reduce((propertiesList, property: keyof IDatasetProperties) => { if (['elements', 'view_depends_on'].includes(property)) { return propertiesList; } const typeofPropertyIsNotObject = typeof properties[property] !== 'object'; const connectionURL = properties['connectionURL']; let value: string | Element; let listItem: IPropertyItem = { isSelectController: ['view_expanded_text', 'viewSqlText'].includes(property), key: property, value: window.JsonHuman.format(properties[property]) }; if (typeofPropertyIsNotObject) { if (connectionURL) { const list = connectionURL.split(',') || []; value = list.length ? window.JsonHuman.format(list) : connectionURL; } else { const tempValue = formatPropertyDateValue(property, properties[property]); value = !tempValue && tempValue !== 0 ? 'NULL' : tempValue; } listItem = { ...listItem, value }; } return [...propertiesList, listItem]; }, []); }; /** * Reads the dataset properties returned by the properties endpoint and builds a list of IPropertyItem values * @param {number} id * @returns {Promise>} */ const readNonPinotProperties = async (id: number): Promise> => { try { return buildPropertiesList(await readDatasetProperties(id)); } catch (e) { warn('Exception occurred building the properties list for non pinot properties'); throw e; } }; /** * Describes the interface of object returned from the api request to get pinot properties * @interface IDatasetSamplesAndColumns */ interface IDatasetSamplesAndColumns { hasSamples: boolean; samples: Array; columns: Array; } /** * Extracts samples and columns for a dataset that is sourced from pinot * @param {IDatasetPinotProperties} [properties={}] * @returns */ const getDatasetSamplesAndColumns = ( properties: IDatasetPinotProperties = {} ): IDatasetSamplesAndColumns | void => { const { elements = [{ columnNames: [], results: [] }] } = properties; const [{ columnNames = [], results }] = elements; if (columnNames.length) { return { hasSamples: true, // TODO: remove the, can be derived from samples.length samples: results, columns: columnNames }; } }; /** * Reads a subset: samples and columns from a datasets properties that are derived from pinot * @param {number} id the id of the pinot dataset * @returns */ const readPinotProperties = async (id: number) => { try { return getDatasetSamplesAndColumns(await readDatasetProperties(id)); } catch (e) { warn('Exception occurred building the samples and columns for pinot properties'); throw e; } }; /** * Persists the changes to a datasets deprecation properties by urn * @param {string} urn * @param {boolean} deprecated * @param {string} deprecationNote * @return {Promise} */ const updateDatasetDeprecationByUrn = (urn: string, deprecated: boolean, deprecationNote: string = ''): Promise => putJSON({ url: datasetDeprecationUrlByUrn(urn), data: { deprecated, deprecationNote } }); export { readDatasetProperties, readNonPinotProperties, readPinotProperties, updateDatasetDeprecationByUrn };