diff --git a/wherehows-web/app/components/dataset-compliance.js b/wherehows-web/app/components/dataset-compliance.js index 9b5ee00518..b1956f30c0 100644 --- a/wherehows-web/app/components/dataset-compliance.js +++ b/wherehows-web/app/components/dataset-compliance.js @@ -1,5 +1,6 @@ import Ember from 'ember'; import isTrackingHeaderField from 'wherehows-web/utils/validators/tracking-headers'; +import { getPlatformFromUrn } from 'wherehows-web/utils/validators/urn'; import { classifiers, datasetClassifiers, @@ -189,6 +190,15 @@ export default Component.extend({ return get(this, 'isNewComplianceInfo') ? 'showAll' : 'showReview'; }), + /** + * Extracts the dataset platform from the dataset urn + * @type {Ember.ComputedProperty} + * @return {string | void} + */ + datasetPlatform: computed('complianceInfo.datasetUrn', function() { + return getPlatformFromUrn(get(this, 'complianceInfo.datasetUrn')); + }), + /** * Reference to the application notifications Service * @type {Ember.Service} @@ -723,6 +733,26 @@ export default Component.extend({ return sourceDatasetClassification; }, + /** + * Display a modal dialog requesting that the user check affirm that the purge type is exempt + * @return {Promise} + */ + showPurgeExemptionWarning() { + const dialogActions = {}; + + get(this, 'notifications').notify('confirm', { + header: 'Confirm purge exemption', + content: + 'By choosing this option you understand that either Legal or HSEC may contact you to verify the purge exemption', + dialogActions + }); + + return new Promise((resolve, reject) => { + dialogActions['didConfirm'] = () => resolve(); + dialogActions['didDismiss'] = () => reject(); + }); + }, + actions: { /** * Sets each datasetClassification value as false @@ -957,6 +987,15 @@ export default Component.extend({ return set(this.getDatasetClassificationRef(), classifier, value); }, + /** + * Updates the complianceType on the compliance policy + * @param {PurgePolicy} purgePolicy + */ + onDatasetPurgePolicyChange(purgePolicy) { + // directly set the complianceType to the updated value + return set(this, 'complianceInfo.complianceType', purgePolicy); + }, + /** * If all validity checks are passed, invoke onSave action on controller */ diff --git a/wherehows-web/app/components/purge-policy.ts b/wherehows-web/app/components/purge-policy.ts new file mode 100644 index 0000000000..156f35e3db --- /dev/null +++ b/wherehows-web/app/components/purge-policy.ts @@ -0,0 +1,80 @@ +import Ember from 'ember'; +import { baseCommentEditorOptions, PurgePolicy, purgePolicyProps } from 'wherehows-web/constants'; +import noop from 'wherehows-web/utils/noop'; + +const { Component, get, set } = Ember; + +/** + * A cache for the exempt policy + * @type {PurgePolicy} + */ +const exemptPolicy = PurgePolicy.PurgeExempt; + +/** + * Checks that a purge policy is exempt + * @param {PurgePolicy} policy the policy to check + */ +const isExempt = (policy: PurgePolicy) => policy === exemptPolicy; + +export default Component.extend({ + tagName: 'ul', + + classNames: ['purge-policy-list'], + + exemptPolicy, + + purgePolicyProps, + + /** + * The dataset's platform + */ + platform: null, + + /** + * The currently save policy for the dataset purge + */ + purgePolicy: null, + + /** + * An options hash for the purge exempt reason text editor + * @type {} + */ + editorOptions: { + ...baseCommentEditorOptions, + placeholder: { + text: 'Please provide an explanation for why this dataset is marked "Purge Exempt" status' + } + }, + + /** + * Action to handle policy change, by default a no-op function + * @type {Function} + */ + onPolicyChange: noop, + + didReceiveAttrs() { + this._super(...arguments); + this.checkExemption(get(this, 'purgePolicy')); + }, + + /** + * Checks that the selected purge policy is exempt, if so, set the + * flag to request the exemption to true + * @param {PurgePolicy} purgePolicy + */ + checkExemption(purgePolicy: PurgePolicy) { + const exemptionReasonRequested = isExempt(purgePolicy); + set(this, 'requestExemptionReason', exemptionReasonRequested); + }, + + actions: { + /** + * Handles the change to the currently selected purge policy + * @param {string} _name unused name for the radio group + * @param {PurgePolicy} purgePolicy the selected purge policy + */ + onChange(_name: string, purgePolicy: PurgePolicy) { + return get(this, 'onPolicyChange')(purgePolicy); + } + } +}); diff --git a/wherehows-web/app/constants/dataset-platforms.ts b/wherehows-web/app/constants/dataset-platforms.ts new file mode 100644 index 0000000000..d957248487 --- /dev/null +++ b/wherehows-web/app/constants/dataset-platforms.ts @@ -0,0 +1,14 @@ +/** + * The known/supported list of dataset platforms + * @enum {string} + */ +enum DatasetPlatform { + Kafka = 'KAFKA', + Espresso = 'ESPRESSO', + Oracle = 'ORACLE', + MySql = 'MYSQL', + Teradata = 'TERADATA', + HDFS = 'HDFS' +} + +export { DatasetPlatform }; diff --git a/wherehows-web/app/constants/dataset-purge-policy.ts b/wherehows-web/app/constants/dataset-purge-policy.ts new file mode 100644 index 0000000000..00abc3698e --- /dev/null +++ b/wherehows-web/app/constants/dataset-purge-policy.ts @@ -0,0 +1,62 @@ +import { DatasetPlatform } from 'wherehows-web/constants/dataset-platforms'; + +/** + * Available values for the purge policy + * @enum {string} + */ +enum PurgePolicy { + AutoPurge = 'AUTO_PURGE', + ManualPurge = 'MANUAL_PURGE', + AutoLimitedRetention = 'AUTO_LIMITED_RETENTION', + ManualLimitedRetention = 'MANUAL_LIMITED_RETENTION', + PurgeExempt = 'PURGE_EXEMPT' +} + +/** + * Index signature for purge policy properties + */ +type PurgePolicyProperties = { + [K in PurgePolicy]: { + platforms: Array; + desc: string; + displayAs: string; + } +}; + +/** + * Client options for each purge policy + * Lists, the available platforms, a descriptions field and a user friendly name for the purge key + */ +const purgePolicyProps: PurgePolicyProperties = { + AUTO_PURGE: { + platforms: [DatasetPlatform.Teradata, DatasetPlatform.Espresso, DatasetPlatform.HDFS], + desc: 'A centralized system will automatically purge this dataset based on the provided metadata.', + displayAs: 'Auto Purge' + }, + MANUAL_PURGE: { + platforms: [DatasetPlatform.MySql, DatasetPlatform.Espresso, DatasetPlatform.Teradata, DatasetPlatform.HDFS], + desc: '', + displayAs: 'Manual Purge' + }, + AUTO_LIMITED_RETENTION: { + platforms: [DatasetPlatform.Kafka, DatasetPlatform.Teradata, DatasetPlatform.HDFS], + desc: + 'The data platform enforces limited retention. Only choose this option if your dataset ' + + "complies with the platform's limited retention policy.", + displayAs: 'Auto Limited Retention' + }, + MANUAL_LIMITED_RETENTION: { + platforms: [DatasetPlatform.Espresso, DatasetPlatform.Oracle, DatasetPlatform.MySql], + desc: '', + displayAs: 'Manual Limited Retention' + }, + PURGE_EXEMPT: { + platforms: Object.keys(DatasetPlatform).map((k: keyof typeof DatasetPlatform) => DatasetPlatform[k]), + desc: + 'The dataset is exempted from purging due to legal or financial requirements.' + + " Only choose this option if you've received an explicit exemption from legal.", + displayAs: 'Purge Exempt' + } +}; + +export { PurgePolicy, purgePolicyProps }; diff --git a/wherehows-web/app/constants/index.ts b/wherehows-web/app/constants/index.ts index 4e46422948..3450547b7e 100644 --- a/wherehows-web/app/constants/index.ts +++ b/wherehows-web/app/constants/index.ts @@ -3,3 +3,5 @@ export * from 'wherehows-web/constants/dataset-classification'; export * from 'wherehows-web/constants/dataset-compliance'; export * from 'wherehows-web/constants/application'; export * from 'wherehows-web/constants/dataset-comments'; +export * from 'wherehows-web/constants/dataset-purge-policy'; +export * from 'wherehows-web/constants/dataset-platforms'; diff --git a/wherehows-web/app/styles/abstracts/_mixins.scss b/wherehows-web/app/styles/abstracts/_mixins.scss index e750e719d2..5f4fb643e1 100644 --- a/wherehows-web/app/styles/abstracts/_mixins.scss +++ b/wherehows-web/app/styles/abstracts/_mixins.scss @@ -44,11 +44,7 @@ $breakpoint-query: map-get($breakpoints, $breakpoint); @if $breakpoint-query { - $query: if( - type-of($breakpoint-query) == 'string', - unquote($breakpoint-query), - inspect($breakpoint-query) - ); + $query: if(type-of($breakpoint-query) == 'string', unquote($breakpoint-query), inspect($breakpoint-query)); @media #{$query} { @content; @@ -84,6 +80,34 @@ overflow-y: auto; } +/// Proxy for AD container styles +@mixin nacho-container { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); + background-color: set-color(white, base); + background-clip: padding-box; + border-radius: 2px; + padding: item-spacing(4); + transition: box-shadow 83ms; +} + +/// Applies rules to render an element with a pill shape +/// @param {Color} $bg-color the background color for the pill +/// @param {Color} $color font/text color +/// @param {Color} $border-color=$bg-color the color to apply to the pill border +///@example - Usage +/// .foo { +/// @include pill(get-color(gray1), get-color(gray6)); +/// } +@mixin pill($bg-color, $color, $border-color: $bg-color) { + border-radius: 32px; + background-color: $bg-color; + border: 1px solid $border-color; + color: $color; + display: inline-block; + line-height: item-spacing(3); + padding: 1px 10px; +} + /// Temp implementation before Auto Prefixer is installed /// @link https://github.com/postcss/autoprefixer @@ -99,7 +123,6 @@ #{$property}: $value; } - /// Maintains the aspect ratio for a wrapped element /// @param {Number} $width number value for the container's width ratio /// @param {Number} $height number value for the container's with ratio @@ -107,7 +130,7 @@ position: relative; &::before { - content: ""; + content: ''; display: block; width: 100%; padding-top: ($height / $width) * 100%; diff --git a/wherehows-web/app/styles/components/_all.scss b/wherehows-web/app/styles/components/_all.scss index 871dbd4187..ebf21cad1f 100644 --- a/wherehows-web/app/styles/components/_all.scss +++ b/wherehows-web/app/styles/components/_all.scss @@ -11,6 +11,7 @@ @import 'dataset-comments/all'; @import 'comments/all'; @import 'empty-state/all'; +@import 'dataset-purge-policy/all'; @import 'nacho/nacho-button'; @import 'nacho/nacho-global-search'; diff --git a/wherehows-web/app/styles/components/dataset-purge-policy/_all.scss b/wherehows-web/app/styles/components/dataset-purge-policy/_all.scss new file mode 100644 index 0000000000..2c2859411e --- /dev/null +++ b/wherehows-web/app/styles/components/dataset-purge-policy/_all.scss @@ -0,0 +1 @@ +@import 'purge-policy-list'; diff --git a/wherehows-web/app/styles/components/dataset-purge-policy/_purge-policy-list.scss b/wherehows-web/app/styles/components/dataset-purge-policy/_purge-policy-list.scss new file mode 100644 index 0000000000..d9f7a4cb31 --- /dev/null +++ b/wherehows-web/app/styles/components/dataset-purge-policy/_purge-policy-list.scss @@ -0,0 +1,21 @@ +.purge-policy-list { + margin: 0; + padding: 0; + + &__item { + @include nacho-container; + list-style-type: none; + margin-bottom: item-spacing(4); + + &--disabled { + color: set-color(grey, mid); + background-color: set-color(grey, light); + } + } + + &__platform { + &--unavailable { + @include pill(set-color(red, maroonflush), set-color(white, base)); + } + } +} diff --git a/wherehows-web/app/templates/components/dataset-compliance.hbs b/wherehows-web/app/templates/components/dataset-compliance.hbs index 59bd8231b4..ce3d825639 100644 --- a/wherehows-web/app/templates/components/dataset-compliance.hbs +++ b/wherehows-web/app/templates/components/dataset-compliance.hbs @@ -102,6 +102,15 @@ {{#if (or isReadOnly isShowingComplianceEditMode)}} {{partial "datasets/dataset-compliance/dataset-compliance-entities"}} {{/if}} + + {{#if isShowingCompliancePurgePolicy}} + {{purge-policy + platform=datasetPlatform + purgeNote=complianceInfo.compliancePurgeNote + purgePolicy=(readonly complianceInfo.complianceType) + onPolicyChange=(action "onDatasetPurgePolicyChange") + }} + {{/if}} {{yield}} diff --git a/wherehows-web/app/utils/datasets/compliance-policy.js b/wherehows-web/app/utils/datasets/compliance-policy.js index 32281c8d3e..f9a2e08397 100644 --- a/wherehows-web/app/utils/datasets/compliance-policy.js +++ b/wherehows-web/app/utils/datasets/compliance-policy.js @@ -13,12 +13,9 @@ const createInitialComplianceInfo = datasetId => ({ datasetId, // default to first item in compliance types list complianceType: 'AUTO_PURGE', + compliancePurgeNote: '', complianceEntities: [], - fieldClassification: {}, - datasetClassification: {}, - geographicAffinity: { affinity: '' }, - recordOwnerType: '', - retentionPolicy: { retentionType: '' } + datasetClassification: {} }); /** diff --git a/wherehows-web/app/utils/noop.ts b/wherehows-web/app/utils/noop.ts new file mode 100644 index 0000000000..1deb066c07 --- /dev/null +++ b/wherehows-web/app/utils/noop.ts @@ -0,0 +1,4 @@ +/** + * exports a noop that can be used in place of Ember.K which is currently deprecated. + */ +export default () => {}; diff --git a/wherehows-web/app/utils/validators/urn.ts b/wherehows-web/app/utils/validators/urn.ts index 2183442b54..93f7cb502e 100644 --- a/wherehows-web/app/utils/validators/urn.ts +++ b/wherehows-web/app/utils/validators/urn.ts @@ -14,8 +14,23 @@ const specialFlowUrnRegex = /(?:\?urn=)([a-z0-9_\-/{}\s]+)/i; /** * Asserts that a provided string matches the urn pattern above - * @param {String} candidateUrn the string to test on + * @param {string} candidateUrn the string to test on */ -export default (candidateUrn: string) => urnRegex.test(String(candidateUrn)); +const isUrn = (candidateUrn: string) => urnRegex.test(String(candidateUrn)); -export { urnRegex, specialFlowUrnRegex }; +/** + * Extracts the platform string from the candidate urn string + * @param {string} candidateUrn the urn string with leading platform identifier + * @returns {string | void} + */ +const getPlatformFromUrn = (candidateUrn: string) => { + const matches = urnRegex.exec(candidateUrn); + if (matches) { + const [, platform] = matches; + return platform.toUpperCase(); + } +}; + +export default isUrn; + +export { urnRegex, specialFlowUrnRegex, getPlatformFromUrn };