| 
									
										
										
										
											2017-10-25 02:11:28 -07:00
										 |  |  | import { warn } from '@ember/debug'; | 
					
						
							|  |  |  | import { ApiStatus } from 'wherehows-web/utils/api'; | 
					
						
							|  |  |  | import { getJSON, putJSON } from 'wherehows-web/utils/api/fetcher'; | 
					
						
							|  |  |  | import { datasetUrlById } 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`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const datasetDeprecationUrlById = (id: number) => `${datasetUrlById(id)}/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 <T extends IDatasetPropertiesGetResponse | IDatasetPinotPropertiesGetResponse>( | 
					
						
							|  |  |  |   id: number | 
					
						
							| 
									
										
										
										
											2017-11-16 14:54:06 -08:00
										 |  |  | ): Promise<IDatasetProperties> => { | 
					
						
							|  |  |  |   const { status, properties, message } = await getJSON<T>({ url: datasetPropertiesUrlById(id) }); | 
					
						
							| 
									
										
										
										
											2017-10-25 02:11:28 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if (status === ApiStatus.OK && properties) { | 
					
						
							|  |  |  |     return properties; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-16 14:54:06 -08:00
										 |  |  |   // treat the error status with a record not found msg as empty set
 | 
					
						
							|  |  |  |   if (status === ApiStatus.ERROR && message === 'record not found') { | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-25 02:11:28 -07:00
										 |  |  |   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) => { | 
					
						
							|  |  |  |       const [postfix, ...range] = props; | 
					
						
							|  |  |  |       return dateString + value.substring(...range) + postfix; | 
					
						
							|  |  |  |     }, ''); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return value; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Builds a list of IPropertyItem values | 
					
						
							|  |  |  |  * @param {IDatasetProperties} properties | 
					
						
							|  |  |  |  * @returns {Array<IPropertyItem>} | 
					
						
							|  |  |  |  * @link IPropertyItem | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const buildPropertiesList = (properties: IDatasetProperties): Array<IPropertyItem> => { | 
					
						
							|  |  |  |   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<Array<IPropertyItem>>} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const readNonPinotProperties = async (id: number): Promise<Array<IPropertyItem>> => { | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     return buildPropertiesList(<IDatasetProperties>await readDatasetProperties(id)); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     warn('Exception occurred building the properties list for non pinot properties'); | 
					
						
							|  |  |  |     throw e; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Extracts samples and columns for a dataset that is sourced from pinot | 
					
						
							|  |  |  |  * @param {IDatasetPinotProperties} properties | 
					
						
							|  |  |  |  * @returns | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const getDatasetSamplesAndColumns = (properties: IDatasetPinotProperties) => { | 
					
						
							|  |  |  |   const { elements = [] } = 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(<IDatasetPinotProperties>await readDatasetProperties(id)); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     warn('Exception occurred building the samples and columns for pinot properties'); | 
					
						
							|  |  |  |     throw e; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Updates the properties on the dataset for deprecation | 
					
						
							|  |  |  |  * @param {number} id the id of the dataset | 
					
						
							|  |  |  |  * @param {boolean} deprecated flag indicating deprecation | 
					
						
							|  |  |  |  * @param {string} [deprecationNote=''] optional note accompanying deprecation change | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const updateDatasetDeprecation = async (id: number, deprecated: boolean, deprecationNote: string = '') => { | 
					
						
							|  |  |  |   const { status, msg } = await putJSON<{ status: ApiStatus; msg: string }>({ | 
					
						
							|  |  |  |     url: datasetDeprecationUrlById(id), | 
					
						
							|  |  |  |     data: { | 
					
						
							|  |  |  |       deprecated, | 
					
						
							|  |  |  |       deprecationNote | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (status !== ApiStatus.OK) { | 
					
						
							|  |  |  |     throw new Error(msg); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export { readDatasetProperties, readNonPinotProperties, readPinotProperties, updateDatasetDeprecation }; |