import Component from '@ember/component'; import { get, set, setProperties, getProperties } from '@ember/object'; import ComputedProperty from '@ember/object/computed'; import { inject } from '@ember/service'; import { task, TaskInstance } from 'ember-concurrency'; import { action } from 'ember-decorators/object'; import Notifications, { NotificationEvent } from 'wherehows-web/services/notifications'; import { IDatasetColumn } from 'wherehows-web/typings/api/datasets/columns'; import { IComplianceInfo, IComplianceSuggestion } from 'wherehows-web/typings/api/datasets/compliance'; import { IDatasetView } from 'wherehows-web/typings/api/datasets/dataset'; import { IDatasetSchema } from 'wherehows-web/typings/api/datasets/schema'; import { IComplianceDataType } from 'wherehows-web/typings/api/list/compliance-datatypes'; import { IReadComplianceResult, notFoundApiError, readDatasetComplianceByUrn, readDatasetComplianceSuggestionByUrn, saveDatasetComplianceByUrn } from 'wherehows-web/utils/api'; import { columnDataTypesAndFieldNames } from 'wherehows-web/utils/api/datasets/columns'; import { readDatasetSchemaByUrn } from 'wherehows-web/utils/api/datasets/schema'; import { readComplianceDataTypes } from 'wherehows-web/utils/api/list/compliance-datatypes'; import { compliancePolicyStrings, removeReadonlyAttr, filterEditableEntities } from 'wherehows-web/constants'; const { successUpdating, failedUpdating } = compliancePolicyStrings; export default class DatasetComplianceContainer extends Component { /** * External action on parent */ setOnChangeSetChange: (hasSuggestions: boolean) => void; /** * External action on parent */ setOnComplianceType: (isNewComplianceInfo: boolean) => void; /** * External action on parent */ setOnChangeSetDrift: (hasDrift: boolean) => void; /** * Mapping of field names on the dataset schema to the respective dataType * @type {Array>} */ schemaFieldNamesMappedToDataTypes: Array> = []; /** * List of compliance data-type objects * @type {Array} */ complianceDataTypes: Array = []; /** * Flag indicating that the compliance information for this dataset is new * @type {boolean} */ isNewComplianceInfo: boolean = false; /** * Object containing the suggested values for the compliance form * @type {IComplianceSuggestion | void} */ complianceSuggestion: IComplianceSuggestion | void; /** * Reference to the application notifications Service * @type {ComputedProperty} */ notifications: ComputedProperty = inject(); /** * Object containing the compliance information for the dataset * @type {IComplianceInfo | void} */ complianceInfo: IComplianceInfo | void; /** * The platform / db that the dataset is persisted * @type {IDatasetView.platform} */ platform: IDatasetView['platform']; /** * Flag indicating if the dataset has a schema representation * @type {boolean} */ schemaless: boolean = false; /** * The nativeName of the dataset * @type {string} */ datasetName: string = ''; /** * The urn identifier for the dataset * @type {string} */ urn: string; didInsertElement() { get(this, 'getContainerDataTask').perform(); } didUpdateAttrs() { get(this, 'getContainerDataTask').perform(); } /** * An async parent task to group all data tasks for this container component * @type {Task>, (a?: any) => TaskInstance>>>} */ getContainerDataTask = task(function*( this: DatasetComplianceContainer ): IterableIterator>> { const { notify } = get(this, 'notifications'); const tasks = Object.values( getProperties(this, [ 'getComplianceTask', 'getComplianceDataTypesTask', 'getComplianceSuggestionsTask', 'getDatasetSchemaTask' ]) ); try { yield* tasks.map(task => task.perform()); } catch (e) { notify(NotificationEvent.info, { content: e }); } }).drop(); /** * Reads the compliance properties for the dataset * @type {Task, (a?: any) => TaskInstance>>} */ getComplianceTask = task(function*( this: DatasetComplianceContainer ): IterableIterator> { const { isNewComplianceInfo, complianceInfo } = yield readDatasetComplianceByUrn(get(this, 'urn')); this.onCompliancePolicyStateChange(isNewComplianceInfo); setProperties(this, { isNewComplianceInfo, complianceInfo }); }); /** * Reads the compliance data types * @type {Task>, (a?: any) => TaskInstance>>>} */ getComplianceDataTypesTask = task(function*( this: DatasetComplianceContainer ): IterableIterator>> { const complianceDataTypes = yield readComplianceDataTypes(); set(this, 'complianceDataTypes', complianceDataTypes); }); /** * Reads the suggestions for the compliance properties on the dataset * @type {Task, (a?: any) => TaskInstance>>} */ getComplianceSuggestionsTask = task(function*( this: DatasetComplianceContainer ): IterableIterator> { const complianceSuggestion = yield readDatasetComplianceSuggestionByUrn(get(this, 'urn')); set(this, 'complianceSuggestion', complianceSuggestion); }); /** * Reads the schema properties for the dataset * @type {Task, (a?: any) => TaskInstance>>} */ getDatasetSchemaTask = task(function*(this: DatasetComplianceContainer): IterableIterator> { try { const { columns, schemaless } = yield readDatasetSchemaByUrn(get(this, 'urn')); const schemaFieldNamesMappedToDataTypes = columnDataTypesAndFieldNames(columns); setProperties(this, { schemaFieldNamesMappedToDataTypes, schemaless }); } catch (e) { // If this schema is missing, silence exception, otherwise propagate if (!notFoundApiError(e)) { throw e; } } }); /** * Handles user notifications when save succeeds or fails * @template T the return type for the save request * @param {Promise} request async policy save request * @returns {Promise} * @memberof DatasetComplianceContainer */ async notifyOnSave(this: DatasetComplianceContainer, request: Promise): Promise { const { notify } = get(this, 'notifications'); try { await request; notify(NotificationEvent.success, { content: successUpdating }); } catch (e) { notify(NotificationEvent.error, { content: failedUpdating }); } return request; } /** * Persists the updates to the compliance policy on the remote host * @return {Promise} */ @action async savePrivacyCompliancePolicy(this: DatasetComplianceContainer): Promise { const complianceInfo = get(this, 'complianceInfo'); if (complianceInfo) { const { complianceEntities } = complianceInfo; await this.notifyOnSave( saveDatasetComplianceByUrn(get(this, 'urn'), { ...complianceInfo, // filter out readonly entities, then fleece readonly attribute from remaining entities before save complianceEntities: removeReadonlyAttr(filterEditableEntities(complianceEntities)) }) ); this.resetPrivacyCompliancePolicy.call(this); } } /** * Resets the compliance information for the dataset with the previously persisted properties */ @action resetPrivacyCompliancePolicy() { get(this, 'getComplianceTask').perform(); } /** * Invokes external action if field suggestions change * @param {boolean} hasSuggestions */ @action onSuggestionsChanged(hasSuggestions: boolean) { this.setOnChangeSetChange(hasSuggestions); } /** * Invokes external action if compliance info is new or otherwise * @param {boolean} isNewComplianceInfo */ @action onCompliancePolicyStateChange(isNewComplianceInfo: boolean) { this.setOnComplianceType(isNewComplianceInfo); } /** * Invokes external action on compliance policy fields drift * @param {boolean} hasDrift */ @action onCompliancePolicyChangeSetDrift(hasDrift: boolean) { this.setOnChangeSetDrift(hasDrift); } }