506 lines
17 KiB
JavaScript

import Ember from 'ember';
import {
createPrivacyCompliancePolicy,
createSecuritySpecification
} from 'wherehows-web/utils/datasets/functions';
import { makeUrnBreadcrumbs } from 'wherehows-web/utils/entities';
const {
Route,
get,
set,
setProperties,
isPresent,
$: { getJSON }
} = Ember;
// TODO: DSS-6581 Create URL retrieval module
const datasetsUrlRoot = '/api/v1/datasets';
const datasetUrl = id => `${datasetsUrlRoot}/${id}`;
const ownerTypeUrlRoot = '/api/v1/owner/types';
const userSettingsUrlRoot = '/api/v1/user/me';
const partyEntitiesUrl = '/api/v1/party/entities';
const getDatasetColumnUrl = id => `${datasetUrl(id)}/columns`;
const getDatasetPropertiesUrl = id => `${datasetUrl(id)}/properties`;
const getDatasetSampleUrl = id => `${datasetUrl(id)}/sample`;
const getDatasetImpactAnalysisUrl = id => `${datasetUrl(id)}/impacts`;
const getDatasetDependsUrl = id => `${datasetUrl(id)}/depends`;
const getDatasetPartitionsUrl = id => `${datasetUrl(id)}/access`;
const getDatasetReferencesUrl = id => `${datasetUrl(id)}/references`;
const getDatasetOwnersUrl = id => `${datasetUrl(id)}/owners`;
const getDatasetInstanceUrl = id => `${datasetUrl(id)}/instances`;
const getDatasetVersionUrl = (id, dbId) =>
`${datasetUrl(id)}/versions/db/${dbId}`;
const getDatasetSecurityUrl = id => `${datasetUrl(id)}/security`;
const getDatasetComplianceUrl = id => `${datasetUrl(id)}/compliance`;
let getDatasetColumn;
export default Route.extend({
//TODO: DSS-6632 Correct server-side if status:error and record not found but response is 200OK
setupController(controller, model) {
currentTab = 'Datasets';
window.updateActiveTab();
let source = '';
var id = 0;
var urn = '';
/**
* Series of chain-able functions invoked to set set properties on the controller required for dataset tab sections
* @type {{privacyCompliancePolicy: ((id)), securitySpecification: ((id)), datasetSchemaFieldNamesAndTypes: ((id))}}
*/
const fetchThenSetOnController = {
privacyCompliancePolicy(id, controller) {
Promise.resolve(getJSON(getDatasetComplianceUrl(id)))
.then(response => {
const {
msg,
status,
privacyCompliancePolicy = createPrivacyCompliancePolicy()
} = response;
const isNewPrivacyCompliancePolicy = status === 'failed' &&
String(msg).includes('actual 0');
setProperties(controller, {
privacyCompliancePolicy,
isNewPrivacyCompliancePolicy
});
});
return this;
},
securitySpecification(id, controller) {
Promise.resolve(getJSON(getDatasetSecurityUrl(id)))
.then(response => {
const {
msg,
status,
securitySpecification = createSecuritySpecification(id)
} = response;
const isNewSecuritySpecification = status === 'failed' &&
String(msg).includes('actual 0');
setProperties(controller, {
securitySpecification,
isNewSecuritySpecification
});
});
return this;
},
datasetSchemaFieldNamesAndTypes(id, controller) {
Promise.resolve(getJSON(datasetUrl(id))).then(({
dataset: { schema } = { schema: undefined }
} = {}) => {
/**
* Parses a JSON dataset schema representation and extracts the field names and types into a list of maps
* @param {JSON} schema
* @returns {Array.<*>}
*/
function getFieldNamesAndTypesFrom(schema = JSON.stringify({})) {
/**
* schema argument may contain property with name `fields` or `fields` may be nested in `schema` object
* with same name.
* Unfortunately shape is inconsistent depending of dataset queried.
* Use `fields` property if present and is an array, otherwise use `fields` property on `schema`.
* Will default to empty array.
* @param {Array} [nestedFields]
* @param {Array} [fields]
* @returns {Array.<*>}
*/
function getFieldTypeMappingArray(
{ schema: { fields: nestedFields = [] } = { schema: {} }, fields }
) {
fields = Array.isArray(fields) ? fields : nestedFields;
// As above, field may contain a label with string property or a name property
return fields.map(({ label: { string } = {}, name, type }) => ({
name: string || name,
type: Array.isArray(type)
? type[0] === 'null' ? type.slice(1) : type
: [type]
}));
}
schema = JSON.parse(schema);
return [...getFieldTypeMappingArray(schema)];
}
set(
controller,
'datasetSchemaFieldsAndTypes',
getFieldNamesAndTypesFrom(schema)
);
});
return this;
}
};
set(controller, 'hasProperty', false);
if (model && model.id) {
({ id, source, urn } = model);
let { originalSchema = null } = model;
this.controllerFor('datasets').set('detailview', true);
if (originalSchema) {
set(model, 'schema', originalSchema);
}
controller.set('model', model);
} else if (model.dataset) {
({ id, source, urn } = model.dataset);
controller.set('model', model.dataset);
this.controllerFor('datasets').set('detailview', true);
}
// Don't set default zero Ids on controller
if (id) {
controller.set('datasetId', id);
// Creates list of partially applied functions from `fetchThenSetController` and invokes each in turn
Object.keys(fetchThenSetOnController)
.map(funcRef =>
fetchThenSetOnController[funcRef]['bind'](
fetchThenSetOnController,
id,
controller
))
.forEach(func => func());
}
Promise.resolve(getJSON(getDatasetInstanceUrl(id)))
.then(({ status, instances = [] }) => {
if (status === 'ok' && instances.length) {
const [firstInstance = {}] = instances;
const { dbId } = firstInstance;
setProperties(controller, {
instances,
hasinstances: true,
currentInstance: firstInstance,
latestInstance: firstInstance
});
return Promise.resolve(
getJSON(getDatasetVersionUrl(id, dbId))
).then(({ status, versions = [] }) => {
if (status === 'ok' && versions.length) {
const [firstVersion] = versions;
setProperties(controller, {
versions,
hasversions: true,
currentVersion: firstVersion,
latestVersion: firstVersion
});
}
return Promise.reject(
new Error('Dataset versions request failed.')
);
});
}
return Promise.reject(new Error('Dataset instances request failed.'));
})
.catch(() => {
setProperties(controller, {
hasinstances: false,
hasversions: false,
currentInstance: '0',
latestInstance: '0',
currentVersion: '0',
latestVersion: '0'
});
});
if (datasetCommentsComponent) {
datasetCommentsComponent.getComments();
}
// If urn exists, create a breadcrumb list
// TODO: DSS-7068 Refactoring in progress , move this to a computed prop on a container component
// FIXME: DSS-7068 browse.entity?urn route does not exist for last item in breadcrumb i.e. the dataset
// currently being viewed. Should this even be a link in the first place?
if (urn) {
set(controller, 'breadcrumbs', makeUrnBreadcrumbs(urn));
}
// Get the list of ownerTypes from endpoint,
// then prevent display of the `consumer`
// insert on controller
Promise.resolve(getJSON(ownerTypeUrlRoot)).then(
({status, ownerTypes = []}) => {
ownerTypes = ownerTypes.filter(ownerType => String(ownerType).toLowerCase() !== 'consumer')
.sort((a, b) => a.localeCompare(b));
status === 'ok' && set(controller, 'ownerTypes', ownerTypes);
}
);
Promise.resolve(getJSON(userSettingsUrlRoot)).then(({ status, user }) => {
if (status === 'ok') {
// TODO: DSS-6633 Remove `data.user.userSetting.detailDefaultView` from
// '/api/v1/user/me' endpoint
controller.set('currentUser', user);
}
});
getDatasetColumn = id =>
Promise.resolve(getJSON(getDatasetColumnUrl(id)))
.then(({ status, columns }) => {
if (status === 'ok') {
if (columns && columns.length) {
const columnsWithHTMLComments = columns.map(column => {
const { comment } = column;
if (comment) {
// TODO: DSS-6122 Refactor global function reference
column.commentHtml = window.marked(comment).htmlSafe();
}
return column;
});
controller.set('hasSchemas', true);
controller.set('schemas', columnsWithHTMLComments);
// TODO: DSS-6122 Refactor direct method invocation on controller
controller.buildJsonView();
// TODO: DSS-6122 Refactor setTimeout,
// global function reference
setTimeout(window.initializeColumnTreeGrid, 500);
}
return columns;
}
return Promise.reject(new Error('Dataset columns request failed.'));
})
.then(columns => columns.map(({dataType, fullFieldPath}) => ({dataType, fieldName: fullFieldPath})))
.then(set.bind(Ember, controller, 'schemaFieldNamesMappedToDataTypes'))
.catch(() => setProperties(controller, {
hasSchemas: false,
schemas: null
}));
getDatasetColumn(id);
if (source.toLowerCase() !== 'pinot') {
Promise.resolve(getJSON(getDatasetPropertiesUrl(id)))
.then(({ status, properties }) => {
if (status === 'ok' && properties) {
const propertyArray = window.convertPropertiesToArray(
properties
) || [];
if (propertyArray.length) {
controller.set('hasProperty', true);
return controller.set('properties', propertyArray);
}
}
return Promise.reject(
new Error('Dataset properties request failed.')
);
})
.catch(() => controller.set('hasProperty', false));
Promise.resolve(getJSON(getDatasetSampleUrl(id)))
.then(({ status, sampleData = {} }) => {
if (status === 'ok') {
const { sample = [] } = sampleData;
const { length } = sample;
if (length) {
const samplesObject = sample.reduce(
(sampleObject, record, index) =>
((sampleObject[`record${index}`] = record), sampleObject),
{}
);
// TODO: DSS-6122 Refactor global function reference
const node = window.JsonHuman.format(samplesObject);
controller.set('hasSamples', true);
// TODO: DSS-6122 Refactor setTimeout
setTimeout(
function() {
const jsonHuman = document.getElementById(
'datasetSampleData-json-human'
);
if (jsonHuman) {
if (jsonHuman.children && jsonHuman.children.length) {
jsonHuman.removeChild(
jsonHuman.childNodes[jsonHuman.children.length - 1]
);
}
jsonHuman.appendChild(node);
}
},
500
);
}
return;
}
return Promise.reject(new Error('Dataset sample request failed.'));
})
.catch(() => set(controller, 'hasSamples', false));
}
if (source.toLowerCase() === 'pinot') {
Promise.resolve(getJSON(getDatasetPropertiesUrl(id))).then(({
status,
properties = {}
}) => {
if (status === 'ok') {
const { elements = [] } = properties;
const [{ columnNames = [], results } = {}] = elements;
if (columnNames.length) {
return setProperties(controller, {
hasSamples: true,
samples: results,
columns: columnNames
});
}
}
return Promise.reject(new Error('Dataset properties request failed.'));
});
}
Promise.resolve(getJSON(getDatasetImpactAnalysisUrl(id)))
.then(({ status, impacts = [] }) => {
if (status === 'ok') {
if (impacts.length) {
return setProperties(controller, {
hasImpacts: true,
impacts: impacts
});
}
}
return Promise.reject(
new Error('Dataset impact analysis request failed.')
);
})
.catch(() => set(controller, 'hasImpacts', false));
Promise.resolve(getJSON(getDatasetDependsUrl(id)))
.then(({ status, depends = [] }) => {
if (status === 'ok') {
if (depends.length) {
// TODO: DSS-6122 Refactor setTimeout,
// global function reference
setTimeout(window.initializeDependsTreeGrid, 500);
return setProperties(controller, {
depends,
hasDepends: true
});
}
}
return Promise.reject(new Error('Dataset depends request failed.'));
})
.catch(() => set(controller, 'hasDepends', false));
Promise.resolve(getJSON(getDatasetPartitionsUrl(id)))
.then(({ status, access = [] }) => {
if (status === 'ok' && access.length) {
return setProperties(controller, {
hasAccess: true,
accessibilities: access
});
}
return Promise.reject(new Error('Dataset partitions request failed.'));
})
.catch(() => set(controller, 'hasAccess', false));
Promise.resolve(getJSON(getDatasetReferencesUrl(id)))
.then(({ status, references = [] }) => {
if (status === 'ok' && references.length) {
setTimeout(window.initializeReferencesTreeGrid, 500);
return setProperties(controller, {
references,
hasReferences: true
});
}
return Promise.reject(new Error('Dataset references request failed.'));
})
.catch(() => set(controller, 'hasReferences', false));
// Retrieve the current owners of the dataset and store on the controller
Promise.resolve(getJSON(getDatasetOwnersUrl(id)))
.then(({ status, owners = [] }) => {
if (status === 'ok') {
set(controller, 'owners', owners.map(owner => Object.assign({}, owner, {
// Date format returned by api is epoch time in seconds, convert to milliseconds and assign as Date instance
modifiedTime: owner.modifiedTime && new Date(owner.modifiedTime * 1000)
})));
}
})
.then(() => getJSON(partyEntitiesUrl))
.then(({ status, userEntities = [] }) => {
if (status === 'ok' && userEntities.length) {
/**
* @type {Object} userEntitiesMaps hash of userEntities: label -> displayName
*/
const userEntitiesMaps = userEntities.reduce(
(map, { label, displayName }) => ((map[label] = displayName), map),
{}
);
setProperties(controller, {
userEntitiesMaps,
userEntitiesSource: Object.keys(userEntitiesMaps)
});
}
});
},
model: ({ dataset_id }) => {
const datasetUrl = `${datasetsUrlRoot}/${dataset_id}`;
return Promise.resolve(getJSON(datasetUrl))
.then(({ status, dataset, message = '' }) => {
return status === 'ok' && isPresent(dataset) ?
dataset :
Promise.reject(
new Error(
`Request for ${datasetUrl} failed with status: ${status}.
${message}`
)
);
});
},
actions: {
getSchema: function() {
const controller = get(this, 'controller');
const id = get(controller, 'model.id');
set(controller, 'isTable', true);
set(controller, 'isJSON', false);
typeof getDatasetColumn === 'function' && getDatasetColumn(id);
},
getDataset() {
Promise.resolve(
getJSON(datasetUrl(this.get('controller.model.id')))
).then(
({ status, dataset }) =>
status === 'ok' && set(this, 'controller.model', dataset)
);
}
}
});