import Controller from '@ember/controller'; import { computed, set, get, setProperties, getProperties, getWithDefault } from '@ember/object'; import { debug } from '@ember/debug'; import { inject as service } from '@ember/service'; import { run, scheduleOnce } from '@ember/runloop'; import $ from 'jquery'; import { datasetComplianceUrlById, createDatasetComment, readDatasetComments, deleteDatasetComment, updateDatasetComment } from 'wherehows-web/utils/api'; import { encodeUrn } from 'wherehows-web/utils/validators/urn'; import { updateDatasetDeprecation } from 'wherehows-web/utils/api/datasets/properties'; import { readDatasetView } from 'wherehows-web/utils/api/datasets/dataset'; import { readDatasetOwners, updateDatasetOwners } from 'wherehows-web/utils/api/datasets/owners'; import { Tabs } from 'wherehows-web/constants/datasets/shared'; import { action } from 'ember-decorators/object'; const { post, getJSON } = $; // gradual refactor into es class, hence extends EmberObject instance export default class extends Controller.extend({ queryParams: ['urn'], /** * Reference to the application notifications Service * @type {Ember.Service} */ notifications: service(), init() { setProperties(this, { userTypes: [ { name: 'Corporate User', value: 'urn:li:corpuser' }, { name: 'Group User', value: 'urn:li:corpGroup' } ] }); this._super(...arguments); }, isPinot: computed('model.source', function() { var model = this.get('model'); if (model) { if (model.source) { return model.source.toLowerCase() == 'pinot'; } } return false; }), isHDFS: computed('model.urn', function() { var model = this.get('model'); if (model) { if (model.urn) { return model.urn.substring(0, 7) == 'hdfs://'; } } return false; }), isSFDC: computed('model.source', function() { var model = this.get('model'); if (model) { if (model.source) { return model.source.toLowerCase() == 'salesforce'; } } return false; }), lineageUrl: computed('model.id', function() { var model = this.get('model'); if (model) { if (model.id) { return '/lineage/dataset/' + model.id; } } return ''; }), schemaHistoryUrl: computed('model.id', function() { var model = this.get('model'); if (model) { if (model.id) { return '/schemaHistory#/schemas/' + model.id; } } return ''; }), refreshVersions: function(dbId) { var model = this.get('model'); if (!model || !model.id) { return; } var versionUrl = '/api/v1/datasets/' + model.id + '/versions/db/' + dbId; $.get(versionUrl, data => { if (data && data.status == 'ok' && data.versions && data.versions.length > 0) { this.set('hasversions', true); this.set('versions', data.versions); this.set('latestVersion', data.versions[0]); this.changeVersion(data.versions[0]); } else { this.set('hasversions', false); this.set('currentVersion', '0'); this.set('latestVersion', '0'); } }); }, changeVersion: function(version) { _this = this; var currentVersion = _this.get('currentVersion'); var latestVersion = _this.get('latestVersion'); if (currentVersion == version) { return; } var objs = $('.version-btn'); if (objs && objs.length > 0) { for (var i = 0; i < objs.length; i++) { $(objs[i]).removeClass('btn-default'); $(objs[i]).removeClass('btn-primary'); if (version == $(objs[i]).attr('data-value')) { $(objs[i]).addClass('btn-primary'); } else { $(objs[i]).addClass('btn-default'); } } } var model = this.get('model'); if (version != latestVersion) { if (!model || !model.id) { return; } var schemaUrl = '/api/v1/datasets/' + model.id + '/schema/' + version; $.get(schemaUrl, function(data) { if (data && data.status == 'ok') { setTimeout(function() { $('#json-viewer').JSONView(JSON.parse(data.schema_text)); }, 500); } }); } _this.set('currentVersion', version); }, /** * Given a predetermined url id, attempt to persist JSON data at url * @param {string} urlId * @param {object} data * @returns {Promise.} */ saveJson(urlId, data) { const request = { url: this.getUrlFor(urlId), data: JSON.stringify(data), contentType: 'application/json' }; // If the return_code is not 200 reject the Promise return Promise.resolve(post(request)).then( ({ status, msg = '' }) => status === 'ok' || Promise.reject(new Error(msg)) ); }, getUrlFor(urlId) { return { compliance: () => datasetComplianceUrlById(`${get(this, 'datasetId')}`) }[urlId](); }, exceptionOnSave({ message = 'An error occurred' }) { const [error] = arguments; debug(`An error occurred on while updating : ${message}`); throw error; }, async handleDatasetComment(strategy, ...args) { const { datasetId: id, 'notifications.notify': notify } = getProperties(this, [ 'datasetId', 'notifications.notify' ]); const action = { create: createDatasetComment.bind(null, id), destroy: deleteDatasetComment.bind(null, id), modify: updateDatasetComment.bind(null, id) }[strategy]; try { await action(...args); notify('success', { content: 'Success!' }); // refresh the list of comments if successful with updated response set(this, 'datasetComments', await readDatasetComments(id)); return true; } catch (e) { notify('error', { content: e.message }); } return false; }, actions: { /** * Action handler creates a dataset comment with the type and text pas * @param {CommentTypeUnion} type the comment type * @param {string} text the text of the comment * @return {Promise.} true if successful in creating the comment, false otherwise */ async createDatasetComment({ type, text }) { return this.handleDatasetComment.call(this, 'create', { type, text }); }, /** * Deletes a comment from the current dataset * @param {number} commentId the id for the comment to be deleted * @return {Promise.} */ async destroyDatasetComment(commentId) { return this.handleDatasetComment.call(this, 'destroy', commentId); }, /** * Updates a comment on the current dataset * @param commentId * @param updatedComment * @return {Promise.} */ async updateDatasetComment(commentId, updatedComment) { return this.handleDatasetComment.call(this, 'modify', commentId, updatedComment); }, /** * Persists the list of owners, and if successful, updated the owners * on the controller * @param {Array} updatedOwners the list of owner to send * @returns {Promise>} */ async saveOwnerChanges(updatedOwners) { const { datasetId: id, 'notifications.notify': notify } = getProperties(this, [ 'datasetId', 'notifications.notify' ]); const csrfToken = getWithDefault(this, 'csrfToken', '').replace('/', ''); try { await updateDatasetOwners(id, csrfToken, updatedOwners); notify('success', { content: 'Success!' }); // updates the shared state for list of dataset owners return set(this, 'owners', await readDatasetOwners(id)); } catch (e) { notify('error', { content: e.message }); } }, updateVersion: function(version) { this.changeVersion(version); }, updateInstance: function(instance) { var currentInstance = this.get('currentInstance'); var latestInstance = this.get('latestInstance'); if (currentInstance == instance.dbId) { return; } var objs = $('.instance-btn'); if (objs && objs.length > 0) { for (var i = 0; i < objs.length; i++) { $(objs[i]).removeClass('btn-default'); $(objs[i]).removeClass('btn-primary'); if (instance.dbCode == $(objs[i]).attr('data-value')) { $(objs[i]).addClass('btn-primary'); } else { $(objs[i]).addClass('btn-default'); } } } this.set('currentInstance', instance.dbId); this.refreshVersions(instance.dbId); }, /** * Requests the privacyCompliancePolicy for the current dataset id * and sets the result on the controller `privacyCompliancePolicy` property * @returns {Promise.<*>} */ resetPrivacyCompliancePolicy() { return getJSON(this.getUrlFor('compliance'), ({ status, complianceInfo }) => { status === 'ok' && setProperties(this, { complianceInfo, isNewComplianceInfo: false }); }); }, /** * Retrieves the current version of the privacyCompliancePolicy document and invokes an api to persist the document * then updates controller state if successful */ savePrivacyCompliancePolicy() { return this.saveJson('compliance', get(this, 'complianceInfo')) .then(this.actions.resetPrivacyCompliancePolicy.bind(this)) .catch(this.exceptionOnSave); } } }) { /** * Enum of tab properties * @type {Tabs} */ tabIds = Tabs; /** * The currently selected tab in view * @type {Tabs} */ tabSelected; /** * Converts the uri on a model to a usable URN format * @type {ComputedProperty} */ encodedUrn = computed('model', function() { const { uri = '' } = get(this, 'model'); return encodeUrn(uri); }); constructor() { super(...arguments); this.tabSelected || (this.tabSelected = Tabs.Ownership); } /** * Handles user generated tab selection action by transitioning to specified route * @param {Tabs} tabSelected the currently selected tab */ @action tabSelectionChanged(tabSelected) { // if the tab selection is same as current, noop return get(this, 'tabSelected') === tabSelected ? void 0 : this.transitionToRoute(`datasets.dataset.${tabSelected}`, get(this, 'encodedUrn')); } }