mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-16 20:48:35 +00:00
Merge pull request #642 from theseyi/master
reduces latency on user interaction for large field sizes on compliance tab. create separate working copy of compliance entities based on column fields.
This commit is contained in:
commit
f25b378740
@ -2,7 +2,10 @@ module.exports = {
|
|||||||
"extends": "eslint:recommended",
|
"extends": "eslint:recommended",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 8,
|
"ecmaVersion": 8,
|
||||||
"sourceType": "module"
|
"sourceType": "module",
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"experimentalObjectRestSpread": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"predef": [
|
|
||||||
"document",
|
|
||||||
"window",
|
|
||||||
"Promise"
|
|
||||||
],
|
|
||||||
"browser": true,
|
|
||||||
"boss": true,
|
|
||||||
"curly": true,
|
|
||||||
"debug": false,
|
|
||||||
"devel": true,
|
|
||||||
"eqeqeq": true,
|
|
||||||
"evil": true,
|
|
||||||
"forin": false,
|
|
||||||
"immed": false,
|
|
||||||
"laxbreak": false,
|
|
||||||
"newcap": true,
|
|
||||||
"noarg": true,
|
|
||||||
"noempty": false,
|
|
||||||
"nonew": false,
|
|
||||||
"nomen": false,
|
|
||||||
"onevar": false,
|
|
||||||
"plusplus": false,
|
|
||||||
"regexp": false,
|
|
||||||
"undef": true,
|
|
||||||
"sub": true,
|
|
||||||
"strict": false,
|
|
||||||
"white": false,
|
|
||||||
"eqnull": true,
|
|
||||||
"esversion": 6,
|
|
||||||
"unused": true
|
|
||||||
}
|
|
||||||
153
wherehows-web/app/components/dataset-compliance-row.js
Normal file
153
wherehows-web/app/components/dataset-compliance-row.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
import DatasetTableRow from 'wherehows-web/components/dataset-table-row';
|
||||||
|
import { fieldIdentifierTypes, defaultFieldDataTypeClassification } from 'wherehows-web/constants';
|
||||||
|
import {
|
||||||
|
fieldIdentifierTypeIds,
|
||||||
|
logicalTypesForIds,
|
||||||
|
logicalTypesForGeneric
|
||||||
|
} from 'wherehows-web/components/dataset-compliance';
|
||||||
|
|
||||||
|
const { computed, get } = Ember;
|
||||||
|
|
||||||
|
const isMixedId = identifierType => identifierType === fieldIdentifierTypes.generic.value;
|
||||||
|
const isCustomId = identifierType => identifierType === fieldIdentifierTypes.custom.value;
|
||||||
|
|
||||||
|
export default DatasetTableRow.extend({
|
||||||
|
/**
|
||||||
|
* @type {Array} logical id types mapped to options for <select>
|
||||||
|
*/
|
||||||
|
logicalTypesForIds,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array} logical generic types mapped to options for <select>
|
||||||
|
*/
|
||||||
|
logicalTypesForGeneric,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag indicating that the identifier type is a generic value
|
||||||
|
* @type {Ember.computed<boolean>}
|
||||||
|
*/
|
||||||
|
isMixed: computed.equal('field.identifierType', fieldIdentifierTypes.generic.value),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flag indicating that the identifier type is a custom value
|
||||||
|
* @type {Ember.computed<boolean>}
|
||||||
|
*/
|
||||||
|
isCustom: computed.equal('field.identifierType', fieldIdentifierTypes.custom.value),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aliases the identifierField on the field
|
||||||
|
* @type {Ember.computed<string>}
|
||||||
|
*/
|
||||||
|
identifierField: computed.alias('field.identifierField'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aliases the data type for the field
|
||||||
|
* @type {Ember.computed<string>}
|
||||||
|
*/
|
||||||
|
dataType: computed.alias('field.dataType'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the field format drop-down should be disabled based on the type of the field
|
||||||
|
* @type {Ember.computed}
|
||||||
|
*/
|
||||||
|
isFieldFormatDisabled: computed('field.identifierType', function() {
|
||||||
|
const identifierType = get(this, 'field.identifierType');
|
||||||
|
return isMixedId(identifierType) || isCustomId(identifierType);
|
||||||
|
}).readOnly(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a computed value for the field identifierType
|
||||||
|
* isSubject flag on the field is represented as MemberId(Subject Member Id)
|
||||||
|
* @type {Ember.computed<string>}
|
||||||
|
*/
|
||||||
|
identifierType: computed('field.identifierType', 'field.isSubject', function() {
|
||||||
|
const identifierType = get(this, 'field.identifierType');
|
||||||
|
|
||||||
|
return identifierType === fieldIdentifierTypes.member.value && get(this, 'field.isSubject')
|
||||||
|
? fieldIdentifierTypes.subjectMember.value
|
||||||
|
: identifierType;
|
||||||
|
}).readOnly(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of field formats that are determined based on the field identifierType
|
||||||
|
* @type {Ember.computed<Array>}
|
||||||
|
*/
|
||||||
|
fieldFormats: computed('field.identifierType', function() {
|
||||||
|
const identifierType = get(this, 'field.identifierType');
|
||||||
|
const logicalTypesForIds = get(this, 'logicalTypesForIds');
|
||||||
|
|
||||||
|
const mixed = isMixedId(identifierType);
|
||||||
|
const custom = isCustomId(identifierType);
|
||||||
|
|
||||||
|
let fieldFormats = fieldIdentifierTypeIds.includes(identifierType)
|
||||||
|
? logicalTypesForIds
|
||||||
|
: get(this, 'logicalTypesForGeneric');
|
||||||
|
const urnFieldFormat = logicalTypesForIds.findBy('value', 'URN');
|
||||||
|
fieldFormats = mixed ? urnFieldFormat : fieldFormats;
|
||||||
|
fieldFormats = custom ? void 0 : fieldFormats;
|
||||||
|
|
||||||
|
return fieldFormats;
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fields logical type, rendered as an Object
|
||||||
|
* @type {Ember.computed<Object>}
|
||||||
|
*/
|
||||||
|
logicalType: computed('field.logicalType', function() {
|
||||||
|
const fieldFormats = get(this, 'fieldFormats');
|
||||||
|
const logicalType = get(this, 'field.logicalType');
|
||||||
|
|
||||||
|
// Same object reference for equality comparision
|
||||||
|
return Array.isArray(fieldFormats) ? fieldFormats.findBy('value', logicalType) : fieldFormats;
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The field security classification
|
||||||
|
* @type {Ember.computed}
|
||||||
|
*/
|
||||||
|
classification: computed('field.classification', 'field.identifierType', function() {
|
||||||
|
const identifierType = get(this, 'field.identifierType');
|
||||||
|
const mixed = isMixedId(identifierType);
|
||||||
|
// Filtered list of id logical types that end with urn, or have no value
|
||||||
|
const urnFieldFormat = get(this, 'logicalTypesForIds').findBy('value', 'URN');
|
||||||
|
|
||||||
|
return get(this, 'field.classification') || (mixed && defaultFieldDataTypeClassification[urnFieldFormat.value]);
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* Handles UI changes to the field identifierType
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
onFieldIdentifierTypeChange({ value }) {
|
||||||
|
const { onFieldIdentifierTypeChange } = this.attrs;
|
||||||
|
if (typeof onFieldIdentifierTypeChange === 'function') {
|
||||||
|
onFieldIdentifierTypeChange(get(this, 'field'), { value });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the updates when the field logical type changes on this field
|
||||||
|
* @param {Event|null} e
|
||||||
|
*/
|
||||||
|
onFieldLogicalTypeChange(e) {
|
||||||
|
const { value } = e || {};
|
||||||
|
const { onFieldLogicalTypeChange } = this.attrs;
|
||||||
|
if (typeof onFieldLogicalTypeChange === 'function') {
|
||||||
|
onFieldLogicalTypeChange(get(this, 'field'), { value });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles UI change to field security classification
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
onFieldClassificationChange({ value }) {
|
||||||
|
const { onFieldClassificationChange } = this.attrs;
|
||||||
|
if (typeof onFieldClassificationChange === 'function') {
|
||||||
|
onFieldClassificationChange(get(this, 'field'), { value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
compliancePolicyStrings
|
compliancePolicyStrings
|
||||||
} from 'wherehows-web/constants';
|
} from 'wherehows-web/constants';
|
||||||
import { isPolicyExpectedShape } from 'wherehows-web/utils/datasets/functions';
|
import { isPolicyExpectedShape } from 'wherehows-web/utils/datasets/functions';
|
||||||
|
import scrollMonitor from 'scrollmonitor';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
assert,
|
assert,
|
||||||
@ -20,7 +21,6 @@ const {
|
|||||||
setProperties,
|
setProperties,
|
||||||
getProperties,
|
getProperties,
|
||||||
getWithDefault,
|
getWithDefault,
|
||||||
isEmpty,
|
|
||||||
String: { htmlSafe }
|
String: { htmlSafe }
|
||||||
} = Ember;
|
} = Ember;
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ const fieldIdentifierOptions = fieldDisplayKeys.map(fieldIdentifierType => {
|
|||||||
* A list of field identifier types that are Ids i.e member ID, org ID, group ID
|
* A list of field identifier types that are Ids i.e member ID, org ID, group ID
|
||||||
* @type {any[]|Array.<String>}
|
* @type {any[]|Array.<String>}
|
||||||
*/
|
*/
|
||||||
const fieldIdentifierTypeIds = Object.keys(fieldIdentifierTypes)
|
export const fieldIdentifierTypeIds = Object.keys(fieldIdentifierTypes)
|
||||||
.map(fieldIdentifierType => fieldIdentifierTypes[fieldIdentifierType])
|
.map(fieldIdentifierType => fieldIdentifierTypes[fieldIdentifierType])
|
||||||
.filter(({ isId }) => isId)
|
.filter(({ isId }) => isId)
|
||||||
.mapBy('value');
|
.mapBy('value');
|
||||||
@ -127,6 +127,12 @@ const cachedLogicalTypes = logicalType =>
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Map logicalTypes to options consumable by DOM
|
||||||
|
export const logicalTypesForIds = cachedLogicalTypes('id');
|
||||||
|
|
||||||
|
// Map generic logical type to options consumable in DOM
|
||||||
|
export const logicalTypesForGeneric = cachedLogicalTypes('generic');
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
sortColumnWithName: 'identifierField',
|
sortColumnWithName: 'identifierField',
|
||||||
filterBy: 'identifierField',
|
filterBy: 'identifierField',
|
||||||
@ -156,6 +162,75 @@ export default Component.extend({
|
|||||||
this.validateAttrs();
|
this.validateAttrs();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
didRender() {
|
||||||
|
this._super(...arguments);
|
||||||
|
// Hides DOM elements that are not currently visible in the UI and unhides them once the user scrolls the
|
||||||
|
// elements into view
|
||||||
|
this.enableDomCloaking();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `lite` / intermediary step to occlusion culling, this helps to improve the rendering of
|
||||||
|
* elements that are currently rendered in the viewport by hiding that aren't.
|
||||||
|
* Setting them to visibility hidden doesn't remove them from the document flow, but the browser
|
||||||
|
* doesn't have to deal with layout for the affected elements since they are off-screen
|
||||||
|
*/
|
||||||
|
enableDomCloaking() {
|
||||||
|
const [dom] = this.$('.dataset-compliance-fields');
|
||||||
|
const triggerCount = 100;
|
||||||
|
if (dom) {
|
||||||
|
const rows = dom.querySelectorAll('tbody tr');
|
||||||
|
|
||||||
|
// if we already have watchers for elements, or if the elements previously cached are no longer valid,
|
||||||
|
// e.g. those elements were destroyed when new data was received, pagination etc
|
||||||
|
if (rows.length > triggerCount && (!this.complianceWatchers || !this.complianceWatchers.has(rows[0]))) {
|
||||||
|
/**
|
||||||
|
* If an item is not in the viewport add a class to occlude it
|
||||||
|
*/
|
||||||
|
const cloaker = function() {
|
||||||
|
if (!this.isInViewport) {
|
||||||
|
return this.watchItem.classList.add('compliance-row--off-screen');
|
||||||
|
}
|
||||||
|
this.watchItem.classList.remove('compliance-row--off-screen');
|
||||||
|
};
|
||||||
|
this.watchers = [];
|
||||||
|
|
||||||
|
// Retain a weak reference to DOM nodes
|
||||||
|
this.complianceWatchers = new WeakMap(
|
||||||
|
[...rows].map(row => {
|
||||||
|
const watcher = scrollMonitor.create(row);
|
||||||
|
watcher['stateChange'](cloaker);
|
||||||
|
cloaker.call(watcher);
|
||||||
|
this.watchers = [...this.watchers, watcher];
|
||||||
|
|
||||||
|
return [watcher.watchItem, watcher];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the artifacts from the dom cloaking operation, drops references held by scroll monitor
|
||||||
|
*/
|
||||||
|
disableDomCloaking() {
|
||||||
|
if (!this.watchers || !Array.isArray(this.watchers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.watchers.forEach(watcher => watcher.destroy());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
willDestroyElement() {
|
||||||
|
this.disableDomCloaking();
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure that props received from on this component
|
* Ensure that props received from on this component
|
||||||
* are valid, otherwise flag
|
* are valid, otherwise flag
|
||||||
@ -173,10 +248,10 @@ export default Component.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Map logicalTypes to options consumable by DOM
|
// Map logicalTypes to options consumable by DOM
|
||||||
idLogicalTypes: cachedLogicalTypes('id'),
|
idLogicalTypes: logicalTypesForIds,
|
||||||
|
|
||||||
// Map generic logical type to options consumable in DOM
|
// Map generic logical type to options consumable in DOM
|
||||||
genericLogicalTypes: cachedLogicalTypes('generic'),
|
genericLogicalTypes: logicalTypesForGeneric,
|
||||||
|
|
||||||
// Map classifiers to options better consumed in DOM
|
// Map classifiers to options better consumed in DOM
|
||||||
classifiers: ['', ...classifiers.sort()].map(value => ({
|
classifiers: ['', ...classifiers.sort()].map(value => ({
|
||||||
@ -196,17 +271,17 @@ export default Component.extend({
|
|||||||
* tracking header.
|
* tracking header.
|
||||||
* Used to indicate to viewer that these fields are hidden.
|
* Used to indicate to viewer that these fields are hidden.
|
||||||
*/
|
*/
|
||||||
containsHiddenTrackingFields: computed('truncatedSchemaFields.length', function() {
|
containsHiddenTrackingFields: computed('truncatedColumnFields.length', function() {
|
||||||
// If their is a diff in complianceDataFields and truncatedSchemaFields,
|
// If their is a diff in schemaFieldNamesMappedToDataTypes and truncatedColumnFields,
|
||||||
// then we have hidden tracking fields
|
// then we have hidden tracking fields
|
||||||
return get(this, 'truncatedSchemaFields.length') !== get(this, 'complianceDataFields.length');
|
return get(this, 'truncatedColumnFields.length') !== get(this, 'schemaFieldNamesMappedToDataTypes.length');
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array.<Object>} Filters the mapped compliance data fields without `kafka type`
|
* @type {Array.<Object>} Filters the mapped compliance data fields without `kafka type`
|
||||||
* tracking headers
|
* tracking headers
|
||||||
*/
|
*/
|
||||||
truncatedSchemaFields: computed('schemaFieldNamesMappedToDataTypes', function() {
|
truncatedColumnFields: computed('schemaFieldNamesMappedToDataTypes', function() {
|
||||||
return getWithDefault(this, 'schemaFieldNamesMappedToDataTypes', []).filter(
|
return getWithDefault(this, 'schemaFieldNamesMappedToDataTypes', []).filter(
|
||||||
({ fieldName }) => !isTrackingHeaderField(fieldName)
|
({ fieldName }) => !isTrackingHeaderField(fieldName)
|
||||||
);
|
);
|
||||||
@ -270,107 +345,76 @@ export default Component.extend({
|
|||||||
* privacyCompliancePolicy.complianceEntities.
|
* privacyCompliancePolicy.complianceEntities.
|
||||||
* The returned list is a map of fields with current or default privacy properties
|
* The returned list is a map of fields with current or default privacy properties
|
||||||
*/
|
*/
|
||||||
complianceDataFields: computed(
|
mergeComplianceEntitiesAndColumnFields(columnIdFieldsToCurrentPrivacyPolicy = {}, truncatedColumnFields = []) {
|
||||||
`${policyComplianceEntitiesKey}.@each.identifierType`,
|
return truncatedColumnFields.map(({ fieldName: identifierField, dataType }) => {
|
||||||
`${policyComplianceEntitiesKey}.[]`,
|
const { [identifierField]: { identifierType, isSubject, logicalType } } = columnIdFieldsToCurrentPrivacyPolicy;
|
||||||
policyFieldClassificationKey,
|
const { [identifierField]: classification } = get(this, policyFieldClassificationKey) || {};
|
||||||
'truncatedSchemaFields',
|
return {
|
||||||
function() {
|
identifierField,
|
||||||
/**
|
dataType,
|
||||||
* Retrieves an attribute on the `policyComplianceEntitiesKey` where the identifierField is the same as the
|
identifierType,
|
||||||
* provided field name
|
isSubject,
|
||||||
* @param attribute
|
logicalType,
|
||||||
* @param fieldName
|
classification
|
||||||
* @return {null}
|
|
||||||
*/
|
|
||||||
const getAttributeOnField = (attribute, fieldName) => {
|
|
||||||
const complianceEntities = get(this, policyComplianceEntitiesKey) || [];
|
|
||||||
// const sourceField = complianceEntities.find(({ identifierField }) => identifierField === fieldName);
|
|
||||||
let sourceField;
|
|
||||||
|
|
||||||
// For long records: >500 elements, the find operation is consistently less performant than a for-loop:
|
|
||||||
// trading elegance for efficiency here
|
|
||||||
for (let i = 0; i < complianceEntities.length; i++) {
|
|
||||||
if (complianceEntities[i]['identifierField'] === fieldName) {
|
|
||||||
sourceField = complianceEntities[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sourceField ? sourceField[attribute] : null;
|
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get value for a list of attributes
|
*
|
||||||
* @param {Array} attributes list of attribute keys to pull from
|
* @param {Array} columnFieldNames
|
||||||
* sourceField
|
* @return {*|{}|any}
|
||||||
* @param {String} fieldName name of the field to lookup
|
*/
|
||||||
* @return {Array} list of attribute values
|
mapColumnIdFieldsToCurrentPrivacyPolicy(columnFieldNames) {
|
||||||
*/
|
const complianceEntities = get(this, policyComplianceEntitiesKey) || [];
|
||||||
const getAttributesOnField = (attributes = [], fieldName) =>
|
const getKeysOnField = (keys = [], fieldName, source = []) => {
|
||||||
attributes.map(attr => getAttributeOnField(attr, fieldName));
|
const sourceField = source.find(({ identifierField }) => identifierField === fieldName) || {};
|
||||||
|
let ret = {};
|
||||||
|
|
||||||
// Set default or if already in policy, retrieve current values from
|
for (const [key, value] of Object.entries(sourceField)) {
|
||||||
// privacyCompliancePolicy.complianceEntities
|
if (keys.includes(key)) {
|
||||||
return getWithDefault(this, 'truncatedSchemaFields', []).map(({ fieldName: identifierField, dataType }) => {
|
ret = { ...ret, [key]: value };
|
||||||
const [identifierType, isSubject, logicalType] = getAttributesOnField(
|
}
|
||||||
['identifierType', 'isSubject', 'logicalType'],
|
}
|
||||||
identifierField
|
return ret;
|
||||||
);
|
};
|
||||||
/**
|
|
||||||
* Flag indicating that the field has an identifierType matching a generic type
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
const isMixed = identifierType === fieldIdentifierTypes.generic.value;
|
|
||||||
/**
|
|
||||||
* Flag indicating that the field has an identifierType matching a custom id type
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
const isCustom = identifierType === fieldIdentifierTypes.custom.value;
|
|
||||||
// Runtime converts the identifierType to subjectMember if the isSubject flag is true
|
|
||||||
const computedIdentifierType = identifierType === fieldIdentifierTypes.member.value && isSubject
|
|
||||||
? fieldIdentifierTypes.subjectMember.value
|
|
||||||
: identifierType;
|
|
||||||
const idLogicalTypes = get(this, 'idLogicalTypes');
|
|
||||||
// Filtered list of id logical types that end with urn, or have no value
|
|
||||||
const urnFieldFormat = idLogicalTypes.findBy('value', 'URN');
|
|
||||||
// Get the current classification list
|
|
||||||
const fieldClassification = get(this, policyFieldClassificationKey) || {};
|
|
||||||
// The field formats applicable to the current identifierType
|
|
||||||
let fieldFormats = fieldIdentifierTypeIds.includes(identifierType)
|
|
||||||
? idLogicalTypes
|
|
||||||
: get(this, 'genericLogicalTypes');
|
|
||||||
/**
|
|
||||||
* If field is a mixed identifier, avail only the urnFieldFormat, otherwise use the prev determined fieldFormats
|
|
||||||
* @type {any|Object}
|
|
||||||
*/
|
|
||||||
fieldFormats = isMixed ? urnFieldFormat : fieldFormats;
|
|
||||||
fieldFormats = isCustom ? void 0 : fieldFormats;
|
|
||||||
/**
|
|
||||||
* An object referencing the fieldFormat for this field
|
|
||||||
* @type {any|Object}
|
|
||||||
*/
|
|
||||||
const logicalTypeObject = Array.isArray(fieldFormats)
|
|
||||||
? fieldFormats.findBy('value', logicalType)
|
|
||||||
: fieldFormats;
|
|
||||||
|
|
||||||
return {
|
return columnFieldNames.reduce((acc, identifierField) => {
|
||||||
dataType,
|
const currentPrivacyAttrs = getKeysOnField(
|
||||||
identifierField,
|
['identifierType', 'isSubject', 'logicalType'],
|
||||||
fieldFormats,
|
identifierField,
|
||||||
// Boolean flag indicating that the list of field formats is unchanging
|
complianceEntities
|
||||||
isFieldFormatDisabled: isMixed || isCustom,
|
);
|
||||||
identifierType: computedIdentifierType,
|
|
||||||
// Check specific use case for urn only field format / logicalType
|
return { ...acc, ...{ [identifierField]: currentPrivacyAttrs } };
|
||||||
classification:
|
}, {});
|
||||||
fieldClassification[identifierField] ||
|
},
|
||||||
(isMixed && defaultFieldDataTypeClassification[urnFieldFormat.value]),
|
|
||||||
// Same object reference for equality comparision
|
/**
|
||||||
logicalType: logicalTypeObject
|
* Computed prop over the current Id fields in the Privacy Policy
|
||||||
};
|
* @type {Ember.computed}
|
||||||
});
|
*/
|
||||||
|
columnIdFieldsToCurrentPrivacyPolicy: computed(
|
||||||
|
'truncatedColumnFields',
|
||||||
|
`${policyComplianceEntitiesKey}.[]`,
|
||||||
|
function() {
|
||||||
|
const columnFieldNames = get(this, 'truncatedColumnFields').map(({ fieldName }) => fieldName);
|
||||||
|
return this.mapColumnIdFieldsToCurrentPrivacyPolicy(columnFieldNames);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches a reference to the generated list of merged data between the column api and the current compliance entities list
|
||||||
|
* @type {Ember.computed}
|
||||||
|
*/
|
||||||
|
mergedComplianceEntitiesAndColumnFields: computed('columnIdFieldsToCurrentPrivacyPolicy', function() {
|
||||||
|
// truncatedColumnFields is a dependency for cp columnIdFieldsToCurrentPrivacyPolicy, so no need to dep on that directly
|
||||||
|
return this.mergeComplianceEntitiesAndColumnFields(
|
||||||
|
get(this, 'columnIdFieldsToCurrentPrivacyPolicy'),
|
||||||
|
get(this, 'truncatedColumnFields')
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that each entity in sourceEntities has a generic
|
* Checks that each entity in sourceEntities has a generic
|
||||||
* @param {Array} sourceEntities = [] the source entities to be matched against
|
* @param {Array} sourceEntities = [] the source entities to be matched against
|
||||||
@ -453,38 +497,49 @@ export default Component.extend({
|
|||||||
// Current list of compliance entities on policy
|
// Current list of compliance entities on policy
|
||||||
const complianceEntities = get(this, policyComplianceEntitiesKey);
|
const complianceEntities = get(this, policyComplianceEntitiesKey);
|
||||||
// All candidate fields that can be on policy, excluding tracking type fields
|
// All candidate fields that can be on policy, excluding tracking type fields
|
||||||
const datasetFields = get(this, 'complianceDataFields');
|
const datasetFields = get(
|
||||||
const fieldsCurrentlyInComplianceList = complianceEntities.mapBy('identifierField');
|
this,
|
||||||
|
'mergedComplianceEntitiesAndColumnFields'
|
||||||
|
).map(({ identifierField, identifierType, isSubject, logicalType }) => ({
|
||||||
|
identifierField,
|
||||||
|
identifierType,
|
||||||
|
isSubject,
|
||||||
|
logicalType
|
||||||
|
}));
|
||||||
// Fields that do not have a logicalType, and no identifierType or identifierType is `fieldIdentifierTypes.none`
|
// Fields that do not have a logicalType, and no identifierType or identifierType is `fieldIdentifierTypes.none`
|
||||||
const unformattedFields = datasetFields.filter(
|
const { formatted, unformatted } = datasetFields.reduce(
|
||||||
({ identifierType, logicalType }) =>
|
({ formatted, unformatted }, field) => {
|
||||||
!logicalType && (fieldIdentifierTypes.none.value === identifierType || !identifierType)
|
const { identifierType, logicalType } = getProperties(field, ['identifierType', 'logicalType']);
|
||||||
|
if (!logicalType && (fieldIdentifierTypes.none.value === identifierType || !identifierType)) {
|
||||||
|
unformatted = [...unformatted, field];
|
||||||
|
} else {
|
||||||
|
formatted = [...formatted, field];
|
||||||
|
}
|
||||||
|
return { formatted, unformatted };
|
||||||
|
},
|
||||||
|
{ formatted: [], unformatted: [] }
|
||||||
);
|
);
|
||||||
let isConfirmed = true;
|
let isConfirmed = true;
|
||||||
|
let unformattedComplianceEntities = [];
|
||||||
|
|
||||||
// If there are unformatted fields, require confirmation from user
|
// If there are unformatted fields, require confirmation from user
|
||||||
if (!isEmpty(unformattedFields)) {
|
if (unformatted.length) {
|
||||||
// Ensure that the unformatted fields to be added to the entities are not already present
|
unformattedComplianceEntities = unformatted.map(({ identifierField }) => ({
|
||||||
// in the previous compliance entities list
|
identifierField,
|
||||||
const unformattedComplianceEntities = unformattedFields
|
identifierType: fieldIdentifierTypes.none.value,
|
||||||
.filter(({ identifierField }) => !fieldsCurrentlyInComplianceList.includes(identifierField))
|
isSubject: null,
|
||||||
.map(({ identifierField }) => ({
|
logicalType: void 0
|
||||||
identifierField,
|
}));
|
||||||
identifierType: fieldIdentifierTypes.none.value,
|
|
||||||
isSubject: null,
|
|
||||||
logicalType: void 0
|
|
||||||
}));
|
|
||||||
|
|
||||||
isConfirmed = confirm(
|
isConfirmed = confirm(
|
||||||
`There are ${unformattedFields.length} non-ID fields that have no field format specified. ` +
|
`There are ${unformatted.length} non-ID fields that have no field format specified. ` +
|
||||||
`Are you sure they don't contain any of the following PII?\n\n` +
|
`Are you sure they don't contain any of the following PII?\n\n` +
|
||||||
`Name, Email, Phone, Address, Location, IP Address, Payment Info, Password, National ID, Device ID etc.`
|
`Name, Email, Phone, Address, Location, IP Address, Payment Info, Password, National ID, Device ID etc.`
|
||||||
);
|
);
|
||||||
|
|
||||||
// If the user confirms that this is ok, apply the unformatted fields on the current compliance list
|
|
||||||
// to be saved
|
|
||||||
isConfirmed && complianceEntities.setObjects([...complianceEntities, ...unformattedComplianceEntities]);
|
|
||||||
}
|
}
|
||||||
|
// If the user confirms that this is ok, apply the unformatted fields on the current compliance list
|
||||||
|
// to be saved
|
||||||
|
isConfirmed && complianceEntities.setObjects([...formatted, ...unformattedComplianceEntities]);
|
||||||
|
|
||||||
return isConfirmed;
|
return isConfirmed;
|
||||||
},
|
},
|
||||||
@ -585,35 +640,20 @@ export default Component.extend({
|
|||||||
* @param {String} identifierType
|
* @param {String} identifierType
|
||||||
*/
|
*/
|
||||||
onFieldIdentifierTypeChange({ identifierField, logicalType }, { value: identifierType }) {
|
onFieldIdentifierTypeChange({ identifierField, logicalType }, { value: identifierType }) {
|
||||||
const complianceList = get(this, policyComplianceEntitiesKey);
|
const currentComplianceEntities = get(this, 'mergedComplianceEntitiesAndColumnFields');
|
||||||
// A reference to the current field in the compliance list if it exists
|
// A reference to the current field in the compliance list if it exists
|
||||||
const currentFieldInComplianceList = complianceList.findBy('identifierField', identifierField);
|
const currentFieldInComplianceList = currentComplianceEntities.findBy('identifierField', identifierField);
|
||||||
const subjectIdString = fieldIdentifierTypes.subjectMember.value;
|
const subjectIdString = fieldIdentifierTypes.subjectMember.value;
|
||||||
// Some rendered identifierTypes may be masks of other underlying types, e.g. subjectId Member type is really
|
// Some rendered identifierTypes may be masks of other underlying types, e.g. subjectId Member type is really
|
||||||
// a memberId with an attribute of isSubject in the affirmative
|
// a memberId with an attribute of isSubject in the affirmative
|
||||||
const unwrappedIdentifierType = subjectIdString === identifierType
|
const unwrappedIdentifierType = subjectIdString === identifierType
|
||||||
? fieldIdentifierTypes.member.value
|
? fieldIdentifierTypes.member.value
|
||||||
: identifierType;
|
: identifierType;
|
||||||
const updatedEntity = Object.assign({}, currentFieldInComplianceList, {
|
setProperties(currentFieldInComplianceList, {
|
||||||
identifierField,
|
|
||||||
identifierType: unwrappedIdentifierType,
|
identifierType: unwrappedIdentifierType,
|
||||||
isSubject: subjectIdString === identifierType ? true : null,
|
isSubject: subjectIdString === identifierType ? true : null,
|
||||||
// If the field is currently not in the complianceList,
|
logicalType: void 0
|
||||||
// we will set the logicalType to be the provided value, otherwise, set to undefined
|
|
||||||
// since the next step removes it from the updated list
|
|
||||||
logicalType: !currentFieldInComplianceList ? logicalType : void 0
|
|
||||||
});
|
});
|
||||||
let transientComplianceList = complianceList;
|
|
||||||
|
|
||||||
// If the identifierField is in the current compliance list,
|
|
||||||
// create a filtered list excluding the identifierField before updating the list
|
|
||||||
if (currentFieldInComplianceList) {
|
|
||||||
transientComplianceList = complianceList.filter(
|
|
||||||
({ identifierField: fieldName }) => fieldName !== identifierField
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
complianceList.setObjects([updatedEntity, ...transientComplianceList]);
|
|
||||||
// Set the defaultClassification for the identifierField,
|
// Set the defaultClassification for the identifierField,
|
||||||
// although the classification is based on the logicalType,
|
// although the classification is based on the logicalType,
|
||||||
// an identifierField may only have one valid logicalType for it's given identifierType
|
// an identifierField may only have one valid logicalType for it's given identifierType
|
||||||
@ -627,23 +667,18 @@ export default Component.extend({
|
|||||||
* @param {Event} e the DOM change event
|
* @param {Event} e the DOM change event
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
onFieldLogicalTypeChange(field, e) {
|
onFieldLogicalTypeChange(field, { value: logicalType } = {}) {
|
||||||
const { identifierField } = field;
|
const { identifierField } = field;
|
||||||
const { value: logicalType } = e || {};
|
|
||||||
let sourceIdentifierField = get(this, policyComplianceEntitiesKey).findBy('identifierField', identifierField);
|
|
||||||
|
|
||||||
// If the identifierField does not current exist, invoke onFieldIdentifierChange to add it on the compliance list
|
// If the identifierField does not current exist, invoke onFieldIdentifierChange to add it on the compliance list
|
||||||
if (!sourceIdentifierField) {
|
if (!field) {
|
||||||
this.actions.onFieldIdentifierTypeChange.call(
|
this.actions.onFieldIdentifierTypeChange.call(
|
||||||
this,
|
this,
|
||||||
{
|
{ identifierField, logicalType },
|
||||||
identifierField,
|
|
||||||
logicalType
|
|
||||||
},
|
|
||||||
{ value: fieldIdentifierTypes.none.value }
|
{ value: fieldIdentifierTypes.none.value }
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
set(sourceIdentifierField, 'logicalType', logicalType);
|
set(field, 'logicalType', logicalType);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.setDefaultClassification({ identifierField }, { value: logicalType });
|
return this.setDefaultClassification({ identifierField }, { value: logicalType });
|
||||||
@ -656,6 +691,10 @@ export default Component.extend({
|
|||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
onFieldClassificationChange({ identifierField }, { value: classification = null }) {
|
onFieldClassificationChange({ identifierField }, { value: classification = null }) {
|
||||||
|
const currentFieldInComplianceList = get(this, 'mergedComplianceEntitiesAndColumnFields').findBy(
|
||||||
|
'identifierField',
|
||||||
|
identifierField
|
||||||
|
);
|
||||||
let fieldClassification = get(this, policyFieldClassificationKey);
|
let fieldClassification = get(this, policyFieldClassificationKey);
|
||||||
let updatedFieldClassification = {};
|
let updatedFieldClassification = {};
|
||||||
// For datasets initially without a fieldClassification, the default value is null
|
// For datasets initially without a fieldClassification, the default value is null
|
||||||
@ -675,6 +714,8 @@ export default Component.extend({
|
|||||||
updatedFieldClassification = Object.assign({}, fieldClassification, { [identifierField]: classification });
|
updatedFieldClassification = Object.assign({}, fieldClassification, { [identifierField]: classification });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply the updated classification value to the current instance of the field in working copy
|
||||||
|
set(currentFieldInComplianceList, 'classification', classification);
|
||||||
set(this, policyFieldClassificationKey, updatedFieldClassification);
|
set(this, policyFieldClassificationKey, updatedFieldClassification);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -33,24 +33,13 @@ export default Route.extend({
|
|||||||
//TODO: DSS-6632 Correct server-side if status:error and record not found but response is 200OK
|
//TODO: DSS-6632 Correct server-side if status:error and record not found but response is 200OK
|
||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
let source = '';
|
let source = '';
|
||||||
var id = 0;
|
let id = 0;
|
||||||
var urn = '';
|
let urn = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Series of chain-able functions invoked to set set properties on the controller required for dataset tab sections
|
* 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 = {
|
const fetchThenSetOnController = {
|
||||||
async complianceInfo(controller, { id }) {
|
|
||||||
const response = await Promise.resolve(getJSON(getDatasetPrivacyUrl(id)));
|
|
||||||
const { msg, status, complianceInfo = createInitialComplianceInfo(id) } = response;
|
|
||||||
const isNewComplianceInfo = status === 'failed' && String(msg).includes('actual 0');
|
|
||||||
|
|
||||||
setProperties(controller, { complianceInfo, isNewComplianceInfo });
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
datasetSchemaFieldNamesAndTypes(controller, { id }) {
|
datasetSchemaFieldNamesAndTypes(controller, { id }) {
|
||||||
Promise.resolve(getJSON(datasetUrl(id))).then(({ dataset: { schema } = { schema: undefined } } = {}) => {
|
Promise.resolve(getJSON(datasetUrl(id))).then(({ dataset: { schema } = { schema: undefined } } = {}) => {
|
||||||
/**
|
/**
|
||||||
@ -132,6 +121,73 @@ export default Route.extend({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.forEach(func => func());
|
.forEach(func => func());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the current compliance policy for the rendered dataset
|
||||||
|
* @param {number} id the id of the dataset
|
||||||
|
* @return {Promise.<{complianceInfo: *, isNewComplianceInfo: boolean}>}
|
||||||
|
*/
|
||||||
|
const getComplianceInfo = async id => {
|
||||||
|
const response = await Promise.resolve(getJSON(getDatasetPrivacyUrl(id)));
|
||||||
|
const { msg, status, complianceInfo = createInitialComplianceInfo(id) } = response;
|
||||||
|
const isNewComplianceInfo = status === 'failed' && String(msg).includes('actual 0');
|
||||||
|
|
||||||
|
return { complianceInfo, isNewComplianceInfo };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the datasetColumn
|
||||||
|
* @param {number} id the id of the dataset
|
||||||
|
*/
|
||||||
|
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 })))
|
||||||
|
.catch(() => setProperties(controller, { hasSchemas: false, schemas: null }));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* async IIFE sets the the complianceInfo and schemaFieldNamesMappedToDataTypes
|
||||||
|
* at once so observers will be buffered
|
||||||
|
* @param {number} id the dataset id
|
||||||
|
* @return {Promise.<void>}
|
||||||
|
*/
|
||||||
|
(async id => {
|
||||||
|
const [columns, privacy] = await Promise.all([getDatasetColumn(id), getComplianceInfo(id)]);
|
||||||
|
const { complianceInfo, isNewComplianceInfo } = privacy;
|
||||||
|
setProperties(controller, {
|
||||||
|
complianceInfo,
|
||||||
|
isNewComplianceInfo,
|
||||||
|
schemaFieldNamesMappedToDataTypes: columns
|
||||||
|
});
|
||||||
|
})(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.resolve(getJSON(getDatasetInstanceUrl(id)))
|
Promise.resolve(getJSON(getDatasetInstanceUrl(id)))
|
||||||
@ -207,48 +263,6 @@ export default Route.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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') {
|
if (source.toLowerCase() !== 'pinot') {
|
||||||
Promise.resolve(getJSON(getDatasetPropertiesUrl(id)))
|
Promise.resolve(getJSON(getDatasetPropertiesUrl(id)))
|
||||||
.then(({ status, properties }) => {
|
.then(({ status, properties }) => {
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
const {
|
const { Route, get, inject: { service } } = Ember;
|
||||||
Route,
|
|
||||||
get,
|
|
||||||
inject: { service }
|
|
||||||
} = Ember;
|
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
session: service(),
|
session: service(),
|
||||||
@ -17,5 +13,16 @@ export default Route.extend({
|
|||||||
if (get(this, 'session.isAuthenticated')) {
|
if (get(this, 'session.isAuthenticated')) {
|
||||||
this.transitionTo('index');
|
this.transitionTo('index');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the default method with a custom op
|
||||||
|
* renders the default template into the login outlet
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
renderTemplate() {
|
||||||
|
this.render({
|
||||||
|
outlet: 'login'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
body {
|
body {
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
font: normal 100 150% / 1.4 $text-font-stack;
|
font: normal 300 150% / 1.4 $text-font-stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
|||||||
@ -16,3 +16,10 @@
|
|||||||
.dataset-field-value {
|
.dataset-field-value {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Visual state for compliance elements
|
||||||
|
.compliance-row {
|
||||||
|
&--off-screen {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,5 +12,5 @@
|
|||||||
{{partial "main"}}
|
{{partial "main"}}
|
||||||
</section>
|
</section>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{outlet}}
|
{{outlet "login"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
{{yield (hash
|
||||||
|
cell=(component 'dataset-table-cell')
|
||||||
|
isFieldFormatDisabled=isFieldFormatDisabled
|
||||||
|
identifierType=identifierType
|
||||||
|
identifierField=identifierField
|
||||||
|
dataType=dataType
|
||||||
|
fieldFormats=fieldFormats
|
||||||
|
logicalType=logicalType
|
||||||
|
classification=classification
|
||||||
|
onFieldIdentifierTypeChange=(action 'onFieldIdentifierTypeChange')
|
||||||
|
onFieldClassificationChange=(action 'onFieldClassificationChange')
|
||||||
|
onFieldLogicalTypeChange=(action 'onFieldLogicalTypeChange')
|
||||||
|
)}}
|
||||||
@ -166,54 +166,56 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{#dataset-table
|
{{#if mergedComplianceEntitiesAndColumnFields.length}}
|
||||||
class="nacho-table--stripped"
|
{{#dataset-table
|
||||||
fields=complianceDataFields
|
class="nacho-table--stripped dataset-compliance-fields"
|
||||||
sortColumnWithName=sortColumnWithName
|
fields=mergedComplianceEntitiesAndColumnFields
|
||||||
filterBy=filterBy
|
sortColumnWithName=sortColumnWithName
|
||||||
sortDirection=sortDirection
|
filterBy=filterBy
|
||||||
searchTerm=searchTerm as |table|
|
sortDirection=sortDirection
|
||||||
}}
|
tableRowComponent='dataset-compliance-row'
|
||||||
<caption>
|
searchTerm=searchTerm as |table|
|
||||||
<input
|
}}
|
||||||
type="text"
|
<caption>
|
||||||
title="Search fields"
|
<input
|
||||||
placeholder="Search fields"
|
type="text"
|
||||||
value="{{table.searchTerm}}"
|
title="Search fields"
|
||||||
oninput={{action table.filterDidChange value="target.value"}}>
|
placeholder="Search fields"
|
||||||
</caption>
|
value="{{table.searchTerm}}"
|
||||||
{{#table.head as |head|}}
|
oninput={{action table.filterDidChange value="target.value"}}>
|
||||||
{{#head.column columnName="identifierField"}}Field{{/head.column}}
|
</caption>
|
||||||
{{#head.column columnName="dataType"}}Data Type{{/head.column}}
|
{{#table.head as |head|}}
|
||||||
{{#head.column class="nacho-table-cell-wrapped"}}
|
{{#head.column columnName="identifierField"}}Field{{/head.column}}
|
||||||
Member, Organization, or Group, ID's
|
{{#head.column columnName="dataType"}}Data Type{{/head.column}}
|
||||||
<a
|
{{#head.column class="nacho-table-cell-wrapped"}}
|
||||||
target="_blank"
|
Member, Organization, or Group, ID's
|
||||||
href="http://go/metadata_acquisition#ProjectOverview-compliance">
|
<a
|
||||||
<sup>
|
target="_blank"
|
||||||
More Info
|
href="http://go/metadata_acquisition#ProjectOverview-compliance">
|
||||||
|
<sup>
|
||||||
|
More Info
|
||||||
|
|
||||||
<span class="glyphicon glyphicon-question-sign"
|
<span class="glyphicon glyphicon-question-sign"
|
||||||
title="More information on various IDs"></span>
|
title="More information on various IDs"></span>
|
||||||
</sup>
|
</sup>
|
||||||
</a>
|
</a>
|
||||||
{{/head.column}}
|
{{/head.column}}
|
||||||
{{#head.column}}
|
{{#head.column}}
|
||||||
Field Format?
|
Field Format?
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="http://go/gdpr-taxonomy#MetadataTaxonomyforDataSets-DatasetLevelTags">
|
href="http://go/gdpr-taxonomy#MetadataTaxonomyforDataSets-DatasetLevelTags">
|
||||||
<sup>
|
<sup>
|
||||||
More Info
|
More Info
|
||||||
|
|
||||||
<span class="glyphicon glyphicon-question-sign"
|
<span class="glyphicon glyphicon-question-sign"
|
||||||
title="More information on Field Format"></span>
|
title="More information on Field Format"></span>
|
||||||
</sup>
|
</sup>
|
||||||
</a>
|
</a>
|
||||||
{{/head.column}}
|
{{/head.column}}
|
||||||
{{#head.column}}
|
{{#head.column}}
|
||||||
Security Classification
|
Security Classification
|
||||||
<sup>
|
<sup>
|
||||||
<span
|
<span
|
||||||
class="glyphicon glyphicon-question-sign"
|
class="glyphicon glyphicon-question-sign"
|
||||||
title={{helpText.classification}}>
|
title={{helpText.classification}}>
|
||||||
@ -223,51 +225,62 @@
|
|||||||
text=helpText.classification
|
text=helpText.classification
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</sup>
|
</sup>
|
||||||
{{/head.column}}
|
{{/head.column}}
|
||||||
{{/table.head}}
|
{{/table.head}}
|
||||||
{{#table.body as |body|}}
|
{{#table.body as |body|}}
|
||||||
{{#each (sort-by table.sortBy table.data) as |field|}}
|
{{#each (sort-by table.sortBy table.data) as |field|}}
|
||||||
{{#body.row as |row|}}
|
{{#body.row
|
||||||
{{#row.cell}}
|
field=field
|
||||||
{{field.identifierField}}
|
onFieldLogicalTypeChange=(action 'onFieldLogicalTypeChange')
|
||||||
{{/row.cell}}
|
onFieldClassificationChange=(action 'onFieldClassificationChange')
|
||||||
{{#row.cell}}
|
onFieldIdentifierTypeChange=(action 'onFieldIdentifierTypeChange')as |row|}}
|
||||||
{{field.dataType}}
|
|
||||||
{{/row.cell}}
|
{{#row.cell}}
|
||||||
{{#row.cell}}
|
{{row.identifierField}}
|
||||||
{{ember-selector
|
{{/row.cell}}
|
||||||
disabled=(not isEditing)
|
|
||||||
values=fieldIdentifierOptions
|
{{#row.cell}}
|
||||||
selected=field.identifierType
|
{{row.dataType}}
|
||||||
selectionDidChange=(action "onFieldIdentifierTypeChange" field)
|
{{/row.cell}}
|
||||||
}}
|
|
||||||
{{/row.cell}}
|
{{#row.cell}}
|
||||||
{{#row.cell}}
|
{{ember-selector
|
||||||
{{#power-select
|
disabled=(not isEditing)
|
||||||
options=field.fieldFormats
|
values=fieldIdentifierOptions
|
||||||
selected=field.logicalType
|
selected=(readonly row.identifierType)
|
||||||
disabled=(or field.isFieldFormatDisabled (not isEditing))
|
selectionDidChange=(action row.onFieldIdentifierTypeChange)
|
||||||
placeholder="Select Format"
|
}}
|
||||||
allowClear=true
|
{{/row.cell}}
|
||||||
searchField="label"
|
|
||||||
triggerClass="ember-power-select-trigger-search"
|
{{#row.cell}}
|
||||||
onchange=(action "onFieldLogicalTypeChange" field) as |fieldFormat|}}
|
{{#power-select
|
||||||
{{fieldFormat.label}}
|
options=row.fieldFormats
|
||||||
{{/power-select}}
|
selected=row.logicalType
|
||||||
{{/row.cell}}
|
disabled=(or row.isFieldFormatDisabled (not isEditing))
|
||||||
{{#row.cell}}
|
placeholder="Select Format"
|
||||||
{{ember-selector
|
allowClear=true
|
||||||
class="nacho-select--hidden-state"
|
searchField="label"
|
||||||
values=classifiers
|
triggerClass="ember-power-select-trigger-search"
|
||||||
selected=field.classification
|
onchange=(action row.onFieldLogicalTypeChange) as |fieldFormat|}}
|
||||||
disabled=(or (not isEditing) (not field.logicalType))
|
{{fieldFormat.label}}
|
||||||
selectionDidChange=(action "onFieldClassificationChange" field)
|
{{/power-select}}
|
||||||
}}
|
{{/row.cell}}
|
||||||
{{/row.cell}}
|
|
||||||
{{/body.row}}
|
{{#row.cell}}
|
||||||
{{/each}}
|
{{ember-selector
|
||||||
{{/table.body}}
|
class="nacho-select--hidden-state"
|
||||||
{{/dataset-table}}
|
values=classifiers
|
||||||
|
selected=row.classification
|
||||||
|
disabled=(or (not isEditing) (not row.logicalType))
|
||||||
|
selectionDidChange=(action row.onFieldClassificationChange)
|
||||||
|
}}
|
||||||
|
{{/row.cell}}
|
||||||
|
{{/body.row}}
|
||||||
|
{{/each}}
|
||||||
|
{{/table.body}}
|
||||||
|
{{/dataset-table}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{yield}}
|
{{yield}}
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
sortDirection=(readonly sortDirection)
|
sortDirection=(readonly sortDirection)
|
||||||
sortDidChange=(action "sortDidChange"))
|
sortDidChange=(action "sortDidChange"))
|
||||||
body=(component tableBodyComponent
|
body=(component tableBodyComponent
|
||||||
tableRowComponent=tableRowComponent
|
tableRowComponent=tableRowComponent)
|
||||||
)
|
|
||||||
foot=(component tableFooterComponent)
|
foot=(component tableFooterComponent)
|
||||||
)}}
|
)}}
|
||||||
|
|||||||
@ -1,424 +0,0 @@
|
|||||||
<div id="dataset" >
|
|
||||||
<div class="well well-sm">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-11">
|
|
||||||
<ul class="breadcrumbs">
|
|
||||||
{{#each breadcrumbs as |crumb|}}
|
|
||||||
<li>
|
|
||||||
<a title="{{crumb.title}}" href="#/datasets/{{crumb.urn}}">
|
|
||||||
{{crumb.title}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-5">
|
|
||||||
<h3>{{ model.name }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-7 text-right">
|
|
||||||
<ul class="datasetDetailsLinks">
|
|
||||||
<li>
|
|
||||||
{{#dataset-favorite dataset=model action="didFavorite"}}
|
|
||||||
{{/dataset-favorite}}
|
|
||||||
<span class="hidden-sm hidden-xs">
|
|
||||||
{{#if model.isFavorite}}
|
|
||||||
Unfavorite
|
|
||||||
{{else}}
|
|
||||||
Favorite
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
{{#if model.hdfsBrowserLink}}
|
|
||||||
<li>
|
|
||||||
<a target="_blank" href={{model.hdfsBrowserLink}}
|
|
||||||
title="View on HDFS">
|
|
||||||
<i class="fa fa-database"></i>
|
|
||||||
<span class="hidden-sm hidden-xs">
|
|
||||||
HDFS
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{/if}}
|
|
||||||
<li>
|
|
||||||
<a target="_blank" href={{lineageUrl}}
|
|
||||||
title="View Lineage">
|
|
||||||
<i class="fa fa-sitemap"></i>
|
|
||||||
<span class="hidden-sm hidden-xs">
|
|
||||||
Lineage
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{#if model.hasSchemaHistory}}
|
|
||||||
<li>
|
|
||||||
<a target="_blank" href={{schemaHistoryUrl}}
|
|
||||||
title="View Schema History">
|
|
||||||
<i class="fa fa-history"></i>
|
|
||||||
<span class="hidden-sm hidden-xs">
|
|
||||||
Schema History
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{/if}}
|
|
||||||
<li>
|
|
||||||
{{#dataset-watch dataset=model getDatasets="getDataset"}}
|
|
||||||
{{/dataset-watch}}
|
|
||||||
<span class="hidden-xs hidden-sm">
|
|
||||||
{{#if model.isWatched}}
|
|
||||||
Unwatch
|
|
||||||
{{else}}
|
|
||||||
Watch
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{dataset-owner-list owners=owners datasetName=model.name}}
|
|
||||||
{{#if hasinstances}}
|
|
||||||
<div class="row">
|
|
||||||
<span class="col-xs-1">Instances:</span>
|
|
||||||
<div class="btn-toolbar col-xs-11" role="toolbar">
|
|
||||||
{{#each instances as |instance index|}}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
{{#if index}}
|
|
||||||
<button type="button" data-value="{{ instance.dbCode }}" class="btn btn-default instance-btn" {{action "updateInstance" instance}}>
|
|
||||||
{{ instance.dbCode }}
|
|
||||||
</button>
|
|
||||||
{{else}}
|
|
||||||
<button type="button" data-value="{{ instance.dbCode }}" class="btn btn-primary instance-btn" {{action "updateInstance" instance}}>
|
|
||||||
{{ instance.dbCode }}
|
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{#if hasversions}}
|
|
||||||
<div class="row">
|
|
||||||
<span class="col-xs-1">Versions:</span>
|
|
||||||
<div class="btn-toolbar col-xs-11" role="toolbar">
|
|
||||||
{{#each versions as |version index|}}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
{{#if index}}
|
|
||||||
<button type="button" data-value="{{ version }}" class="btn btn-default version-btn" {{action "updateVersion" version}}>
|
|
||||||
{{ version }}
|
|
||||||
</button>
|
|
||||||
{{else}}
|
|
||||||
<button type="button" data-value="{{ version }}" class="btn btn-primary version-btn" {{action "updateVersion" version}}>
|
|
||||||
{{ version }}
|
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if tabview}}
|
|
||||||
<ul class="nav nav-tabs nav-justified">
|
|
||||||
{{#unless isPinot}}
|
|
||||||
<li id="properties">
|
|
||||||
<a data-toggle="tab" href="#propertiestab">
|
|
||||||
Properties
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{/unless}}
|
|
||||||
<li id="properties">
|
|
||||||
<a data-toggle="tab" href="#commentstab">
|
|
||||||
Comments
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li id="schemas" class="active"><a data-toggle="tab" href="#schematab">Schema</a></li>
|
|
||||||
<li id="ownership"><a data-toggle="tab" href="#ownertab">Ownership</a></li>
|
|
||||||
{{#unless isSFDC}}
|
|
||||||
<li id="samples"><a data-toggle="tab" href="#sampletab">Sample Data</a></li>
|
|
||||||
{{/unless}}
|
|
||||||
<li id="impacts">
|
|
||||||
<a data-toggle="tab"
|
|
||||||
title="Down Stream Impact Analysis"
|
|
||||||
href="#impacttab">
|
|
||||||
Downstream
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li id="depends">
|
|
||||||
<a data-toggle="tab"
|
|
||||||
title="Relations"
|
|
||||||
href="#dependtab">
|
|
||||||
Relations
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li id="access">
|
|
||||||
<a data-toggle="tab"
|
|
||||||
title="Accessibilities"
|
|
||||||
href="#accesstab">
|
|
||||||
Availability
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li id="compliance">
|
|
||||||
<a data-toggle="tab"
|
|
||||||
title="Compliance"
|
|
||||||
href="#compliancetab">
|
|
||||||
Compliance
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li id="confidential">
|
|
||||||
<a data-toggle="tab"
|
|
||||||
title="Confidential"
|
|
||||||
href="#confidentialtab">
|
|
||||||
Confidential
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="tab-content">
|
|
||||||
{{#unless isPinot}}
|
|
||||||
<div id="propertiestab" class="tab-pane">
|
|
||||||
{{#dataset-property hasProperty=hasProperty properties=properties}}
|
|
||||||
{{/dataset-property}}
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
<div id="commentstab" class="tab-pane">
|
|
||||||
{{#dataset-comments dataset=this}}
|
|
||||||
{{/dataset-comments}}
|
|
||||||
</div>
|
|
||||||
<div id="schematab" class="tab-pane active">
|
|
||||||
{{#dataset-schema hasSchemas=hasSchemas isTable=isTable isJSON=isJSON schemas=schemas dataset=model}}
|
|
||||||
{{/dataset-schema}}
|
|
||||||
</div>
|
|
||||||
<div id="ownertab" class="tab-pane">
|
|
||||||
{{dataset-author owners=owners
|
|
||||||
ownerTypes=ownerTypes
|
|
||||||
showMsg=showMsg
|
|
||||||
alertType=alertType
|
|
||||||
ownerMessage=ownerMessage
|
|
||||||
parentController=this}}
|
|
||||||
</div>
|
|
||||||
{{#unless isSFDC}}
|
|
||||||
<div id="sampletab" class="tab-pane">
|
|
||||||
{{#dataset-sample hasSamples=hasSamples isPinot=isPinot columns=columns samples=samples}}
|
|
||||||
{{/dataset-sample}}
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
<div id="impacttab" class="tab-pane">
|
|
||||||
{{#dataset-impact hasImpacts=hasImpacts impacts=impacts}}
|
|
||||||
{{/dataset-impact}}
|
|
||||||
</div>
|
|
||||||
<div id="dependtab" class="tab-pane">
|
|
||||||
{{#dataset-relations hasDepends=hasDepends depends=depends hasReferences=hasReferences references=references}}
|
|
||||||
{{/dataset-relations}}
|
|
||||||
</div>
|
|
||||||
<div id="accesstab" class="tab-pane">
|
|
||||||
{{#dataset-access hasAccess=hasAccess accessibilities=accessibilities}}
|
|
||||||
{{/dataset-access}}
|
|
||||||
</div>
|
|
||||||
<div id="compliancetab" class="tab-pane">
|
|
||||||
{{dataset-compliance privacyCompliancePolicy=privacyCompliancePolicy
|
|
||||||
isNewPrivacyCompliancePolicy=isNewPrivacyCompliancePolicy
|
|
||||||
datasetSchemaFieldsAndTypes=datasetSchemaFieldsAndTypes
|
|
||||||
onSave=(action "savePrivacyCompliancePolicy")
|
|
||||||
onReset=(action "resetPrivacyCompliancePolicy")}}
|
|
||||||
</div>
|
|
||||||
<div id="confidentialtab" class="tab-pane">
|
|
||||||
{{dataset-confidential securitySpecification=securitySpecification
|
|
||||||
isNewSecuritySpecification=isNewSecuritySpecification
|
|
||||||
datasetSchemaFieldsAndTypes=datasetSchemaFieldsAndTypes
|
|
||||||
onSave=(action "saveSecuritySpecification")
|
|
||||||
onReset=(action "resetSecuritySpecification")}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
|
|
||||||
{{#unless isPinot}}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="propertiesHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#properties" aria-expanded="true" aria-controls="properties">
|
|
||||||
Properties
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="properties" class="panel-collapse collapse" role="tabpanel" aria-labelledby="propertiesHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="row">
|
|
||||||
{{#dataset-property hasProperty=hasProperty properties=properties}}
|
|
||||||
{{/dataset-property}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="commentsHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#comments" aria-expanded="true" aria-controls="comments">
|
|
||||||
Comments
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="comments" class="panel-collapse collapse" role="tabpanel" aria-labelledby="commentsHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#dataset-comments dataset=this}}
|
|
||||||
{{/dataset-comments}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="schemaHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#schema" aria-expanded="false" aria-controls="schema">
|
|
||||||
Schema
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="schema" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="schemaHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#dataset-schema hasSchemas=hasSchemas isTable=isTable isJSON=isJSON schemas=schemas dataset=model}}
|
|
||||||
{{/dataset-schema}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="ownershipHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#ownership" aria-expanded="false" aria-controls="ownership">
|
|
||||||
Ownership
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="ownership" class="panel-collapse collapse" role="tabpanel" aria-labelledby="ownershipHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{dataset-author owners=owners
|
|
||||||
ownerTypes=ownerTypes
|
|
||||||
showMsg=showMsg
|
|
||||||
alertType=alertType
|
|
||||||
ownerMessage=ownerMessage
|
|
||||||
parentController=this}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#unless isSFDC}}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="sampleHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#sampleData" aria-expanded="false" aria-controls="sampleData">
|
|
||||||
Sample Data
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="sampleData" class="panel-collapse collapse" role="tabpanel" aria-labelledby="sampleHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#dataset-sample hasSamples=hasSamples isPinot=isPinot columns=columns samples=samples}}
|
|
||||||
{{/dataset-sample}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="impactsHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#impactAnalysis" title="Down Stream Impact Analysis"
|
|
||||||
aria-expanded="false" aria-controls="sampleData">
|
|
||||||
Downstream
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="impactAnalysis" class="panel-collapse collapse" role="tabpanel" aria-labelledby="impactsHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#dataset-impact hasImpacts=hasImpacts impacts=impacts}}
|
|
||||||
{{/dataset-impact}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="dependsHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#dependsview" aria-expanded="false" aria-controls="sampleData">
|
|
||||||
Relations
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="dependsview" class="panel-collapse collapse" role="tabpanel" aria-labelledby="impactsHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#dataset-relations hasDepends=hasDepends depends=depends hasReferences=hasReferences references=references}}
|
|
||||||
{{/dataset-relations}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="accessHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#accessview" aria-expanded="false" aria-controls="accessData">
|
|
||||||
Availability
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="accessview" class="panel-collapse collapse" role="tabpanel" aria-labelledby="accessHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#dataset-access hasAccess=hasAccess accessibilities=accessibilities}}
|
|
||||||
{{/dataset-access}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="complianceHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#accessview" aria-expanded="false" aria-controls="accessData">
|
|
||||||
Compliance
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="accessview" class="panel-collapse collapse" role="tabpanel" aria-labelledby="complianceHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{dataset-compliance privacyCompliancePolicy=privacyCompliancePolicy
|
|
||||||
isNewPrivacyCompliancePolicy=isNewPrivacyCompliancePolicy
|
|
||||||
datasetSchemaFieldsAndTypes=datasetSchemaFieldsAndTypes
|
|
||||||
onSave=(action "savePrivacyCompliancePolicy")
|
|
||||||
onReset=(action "resetPrivacyCompliancePolicy")}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="confidentialHeading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
|
||||||
href="#accessview" aria-expanded="false" aria-controls="accessData">
|
|
||||||
Confidential
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="accessview" class="panel-collapse collapse" role="tabpanel"
|
|
||||||
aria-labelledby="confidentialHeading">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{dataset-confidential securitySpecification=securitySpecification
|
|
||||||
isNewSecuritySpecification=isNewSecuritySpecification
|
|
||||||
datasetSchemaFieldsAndTypes=datasetSchemaFieldsAndTypes
|
|
||||||
onSave=(action "saveSecuritySpecification")
|
|
||||||
onReset=(action "resetSecuritySpecification")}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
@ -1,117 +1,41 @@
|
|||||||
<div id="pagedDatasets">
|
<div class="row">
|
||||||
<div class="row">
|
{{#unless detailview}}
|
||||||
<div class="col-xs-12">
|
<div class="search-pagination">
|
||||||
{{#unless detailview}}
|
<ul class="pager">
|
||||||
<div class="well well-sm">
|
{{#unless first}}
|
||||||
<div class="row">
|
<li class="previous">
|
||||||
<div class="col-xs-11">
|
{{#if urn}}
|
||||||
<ul class="breadcrumbs">
|
{{#link-to 'datasets.name.subpage' currentName previousPage
|
||||||
{{#each breadcrumbs as |crumb|}}
|
(query-params urn=urn)}}
|
||||||
<li>
|
← Prev
|
||||||
{{#link-to 'datasets.page' crumb.urn title=crumb.title}}
|
{{/link-to}}
|
||||||
{{crumb.title}}
|
{{else}}
|
||||||
{{/link-to}}
|
{{#link-to 'datasets.page' previousPage}}
|
||||||
</li>
|
← Prev
|
||||||
{{/each}}
|
{{/link-to}}
|
||||||
</ul>
|
{{/if}}
|
||||||
</div>
|
</li>
|
||||||
</div>
|
{{/unless}}
|
||||||
</div>
|
<li>
|
||||||
<div class="search-pagination">
|
{{ model.data.count }} datasets - page {{ model.data.page }}
|
||||||
<ul class="pager">
|
of {{ model.data.totalPages }}
|
||||||
{{#unless first}}
|
</li>
|
||||||
<li class="previous">
|
{{#unless last}}
|
||||||
{{#if urn}}
|
<li class="next">
|
||||||
{{#link-to 'datasets.name.subpage' currentName previousPage (query-params urn=urn)}}
|
{{#if urn}}
|
||||||
← Prev
|
{{#link-to 'datasets.name.subpage' currentName nextPage
|
||||||
{{/link-to}}
|
(query-params urn=urn)}}
|
||||||
{{else}}
|
Next →
|
||||||
{{#link-to 'datasets.page' previousPage}}
|
{{/link-to}}
|
||||||
← Prev
|
{{else}}
|
||||||
{{/link-to}}
|
{{#link-to 'datasets.page' nextPage}}
|
||||||
{{/if}}
|
Next →
|
||||||
</li>
|
{{/link-to}}
|
||||||
{{/unless}}
|
{{/if}}
|
||||||
<li>
|
</li>
|
||||||
{{ model.data.count }} datasets - page {{ model.data.page }} of {{ model.data.totalPages }}
|
{{/unless}}
|
||||||
</li>
|
</ul>
|
||||||
{{#unless last}}
|
|
||||||
<li class="next">
|
|
||||||
{{#if urn}}
|
|
||||||
{{#link-to 'datasets.name.subpage' currentName nextPage (query-params urn=urn)}}
|
|
||||||
Next →
|
|
||||||
{{/link-to}}
|
|
||||||
{{else}}
|
|
||||||
{{#link-to 'datasets.page' nextPage}}
|
|
||||||
Next →
|
|
||||||
{{/link-to}}
|
|
||||||
{{/if}}
|
|
||||||
</li>
|
|
||||||
{{/unless}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
{{#unless detailview}}
|
|
||||||
<table class="table table-bordered search-results" style="word-break: break-all;">
|
|
||||||
<tbody>
|
|
||||||
{{#each model.data.datasets as |dataset|}}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
{{#link-to 'datasets.dataset' dataset}}
|
|
||||||
{{ dataset.name }}
|
|
||||||
{{/link-to}}
|
|
||||||
</div>
|
|
||||||
{{#if dataset.owners}}
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<span>owner:</span>
|
|
||||||
{{#each dataset.owners as |owner|}}
|
|
||||||
<p style="display:inline" title={{owner.name}}>{{ owner.userName }}</p>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{#if dataset.formatedModified}}
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<span>last modified:</span>
|
|
||||||
{{ dataset.formatedModified }}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-right">
|
|
||||||
<ul class="datasetTableLinks">
|
|
||||||
<li class="text-center no-link">
|
|
||||||
<span class="source">
|
|
||||||
{{ dataset.source }}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="text-center" title="ownership">
|
|
||||||
{{dataset-owner dataset=dataset action="owned"}}
|
|
||||||
</li>
|
|
||||||
<li class="text-center" title="favorite">
|
|
||||||
{{dataset-favorite dataset=dataset action="didFavorite"}}
|
|
||||||
</li>
|
|
||||||
<li class="text-center" title="watch">
|
|
||||||
{{dataset-watch dataset=dataset getDatasets='getDatasets'}}
|
|
||||||
</li>
|
|
||||||
<li class="text-center">
|
|
||||||
<a href="/lineage/dataset/{{dataset.id}}" title="lineage for {{dataset.name}}">
|
|
||||||
<i class="fa fa-sitemap"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{/unless}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12">
|
{{/unless}}
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@ -16,7 +16,8 @@
|
|||||||
"jsondiffpatch": "^0.2.4",
|
"jsondiffpatch": "^0.2.4",
|
||||||
"marked": "^0.3.6",
|
"marked": "^0.3.6",
|
||||||
"toastr": "^2.1.3",
|
"toastr": "^2.1.3",
|
||||||
"x-editable": "^1.5.1"
|
"x-editable": "^1.5.1",
|
||||||
|
"scrollMonitor": "^1.2.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"ember-cli-shims": "0.1.3",
|
"ember-cli-shims": "0.1.3",
|
||||||
|
|||||||
@ -17,7 +17,7 @@ module.exports = function(defaults) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
fingerprint: {
|
fingerprint: {
|
||||||
enabled: true
|
enabled: EmberApp.env() === 'production'
|
||||||
},
|
},
|
||||||
|
|
||||||
'ember-cli-bootstrap-sassy': {
|
'ember-cli-bootstrap-sassy': {
|
||||||
@ -25,6 +25,7 @@ module.exports = function(defaults) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
minifyJS: {
|
minifyJS: {
|
||||||
|
enabled: EmberApp.env() === 'production',
|
||||||
options: {
|
options: {
|
||||||
exclude: ['**/vendor.js', 'legacy-app/**']
|
exclude: ['**/vendor.js', 'legacy-app/**']
|
||||||
}
|
}
|
||||||
@ -130,6 +131,8 @@ module.exports = function(defaults) {
|
|||||||
app.import('bower_components/jsondiffpatch/public/build/jsondiffpatch.min.js');
|
app.import('bower_components/jsondiffpatch/public/build/jsondiffpatch.min.js');
|
||||||
app.import('bower_components/jsondiffpatch/public/build/jsondiffpatch-formatters.min.js');
|
app.import('bower_components/jsondiffpatch/public/build/jsondiffpatch-formatters.min.js');
|
||||||
app.import('bower_components/x-editable/dist/bootstrap3-editable/js/bootstrap-editable.min.js');
|
app.import('bower_components/x-editable/dist/bootstrap3-editable/js/bootstrap-editable.min.js');
|
||||||
|
app.import('bower_components/scrollMonitor/scrollMonitor.js');
|
||||||
|
app.import('vendor/shims/scrollmonitor.js');
|
||||||
|
|
||||||
return app.toTree(new MergeTrees([faFontTree, bsFontTree, treegridImgTree]));
|
return app.toTree(new MergeTrees([faFontTree, bsFontTree, treegridImgTree]));
|
||||||
};
|
};
|
||||||
|
|||||||
@ -79,7 +79,7 @@
|
|||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"gitDir": "../",
|
"gitDir": "../",
|
||||||
"linters": {
|
"linters": {
|
||||||
"wherehows-web/app/**/*.js": [
|
"wherehows-web/{app,tests}/**/*.js": [
|
||||||
"prettier --print-width 120 --single-quote --write",
|
"prettier --print-width 120 --single-quote --write",
|
||||||
"git add"
|
"git add"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { moduleForComponent, test } from 'ember-qunit';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
moduleForComponent('dataset-compliance-row', 'Integration | Component | dataset compliance row', {
|
||||||
|
integration: true
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders', function(assert) {
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.on('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
this.render(hbs`{{dataset-compliance-row}}`);
|
||||||
|
|
||||||
|
assert.equal(this.$().text().trim(), '');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
this.render(hbs`
|
||||||
|
{{#dataset-compliance-row}}
|
||||||
|
template block text
|
||||||
|
{{/dataset-compliance-row}}
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.equal(this.$().text().trim(), 'template block text');
|
||||||
|
});
|
||||||
9
wherehows-web/vendor/shims/scrollmonitor.js
vendored
Normal file
9
wherehows-web/vendor/shims/scrollmonitor.js
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
(function() {
|
||||||
|
function vendorModule() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
return { 'default': self['scrollMonitor'] };
|
||||||
|
}
|
||||||
|
|
||||||
|
define('scrollmonitor', [], vendorModule);
|
||||||
|
})();
|
||||||
Loading…
x
Reference in New Issue
Block a user