diff --git a/wherehows-web/app/components/browser/browser-rail.js b/wherehows-web/app/components/browser/browser-rail.ts similarity index 50% rename from wherehows-web/app/components/browser/browser-rail.js rename to wherehows-web/app/components/browser/browser-rail.ts index 5d2c6d3c6c..684c07a537 100644 --- a/wherehows-web/app/components/browser/browser-rail.js +++ b/wherehows-web/app/components/browser/browser-rail.ts @@ -1,2 +1,2 @@ import Component from '@ember/component'; -export default Component.extend({}); +export default class extends Component {} diff --git a/wherehows-web/app/components/browser/browser-view.js b/wherehows-web/app/components/browser/browser-summary.ts similarity index 50% rename from wherehows-web/app/components/browser/browser-view.js rename to wherehows-web/app/components/browser/browser-summary.ts index 5d2c6d3c6c..684c07a537 100644 --- a/wherehows-web/app/components/browser/browser-view.js +++ b/wherehows-web/app/components/browser/browser-summary.ts @@ -1,2 +1,2 @@ import Component from '@ember/component'; -export default Component.extend({}); +export default class extends Component {} diff --git a/wherehows-web/app/components/browser/browser-viewport.js b/wherehows-web/app/components/browser/browser-viewport.ts similarity index 50% rename from wherehows-web/app/components/browser/browser-viewport.js rename to wherehows-web/app/components/browser/browser-viewport.ts index 5d2c6d3c6c..684c07a537 100644 --- a/wherehows-web/app/components/browser/browser-viewport.js +++ b/wherehows-web/app/components/browser/browser-viewport.ts @@ -1,2 +1,2 @@ import Component from '@ember/component'; -export default Component.extend({}); +export default class extends Component {} diff --git a/wherehows-web/app/components/browser/containers/browser-rail.js b/wherehows-web/app/components/browser/containers/browser-rail.js deleted file mode 100644 index 0b5e31298a..0000000000 --- a/wherehows-web/app/components/browser/containers/browser-rail.js +++ /dev/null @@ -1,130 +0,0 @@ -import Component from '@ember/component'; -import { connect } from 'ember-redux'; -import { urnRegex, specialFlowUrnRegex } from 'wherehows-web/utils/validators/urn'; - -/** - * Matches string representing a url path segment that contains a `page` segment followed by a page number - * The page number is retained - * @type {RegExp} - */ -const pageRegex = /\/page\/([0-9]+)/i; -const nameRegex = /\/name\/([0-9a-z()_{}\[\]\/\s]+)/i; - -/** - * Takes a node url and parses out the query params and path spec to be included in the link component - * @param {String} nodeUrl url linking to the node - * @return {Object} - */ -const nodeUrlToQueryParams = nodeUrl => { - const pageMatch = nodeUrl.match(pageRegex); - const urnMatch = nodeUrl.match(urnRegex); - const flowUrnMatch = nodeUrl.match(specialFlowUrnRegex); - const nameMatch = nodeUrl.match(nameRegex); - let queryParams = null; - - // If we have a page match, append the page number to eventual urn object - // in most cases this page is usually, 1. Might be safe to remove the page pattern - // search in its entirety if this is the case - if (Array.isArray(pageMatch)) { - queryParams = Object.assign({}, queryParams, { - page: pageMatch[1] - }); - } - - if (Array.isArray(nameMatch)) { - let match = nameMatch[1]; - match = match.split('/page')[0]; - - queryParams = Object.assign({}, queryParams, { - name: match - }); - } - - // If we have a urn match, append the urn to eventual query params object - if (Array.isArray(urnMatch) || Array.isArray(flowUrnMatch)) { - const urn = urnMatch || [flowUrnMatch[1]]; - - queryParams = Object.assign({}, queryParams, { - // Extract the entire match as urn value - urn: urn[0] - }); - } - - return queryParams; -}; - -const getNodes = (entity, state = {}) => query => - ({ - get datasets() { - return state[entity].nodesByUrn[query]; - }, - get metrics() { - return state[entity].nodesByName[query]; - }, - get flows() { - return this.datasets; - } - }[entity]); - -/** - * Selector function that takes a Redux Store to extract - * state props for the browser-rail - * @param {Object} state current app state tree - * @return {{nodes: (*|Array)}} - */ -const stateToComputed = state => { - const header = 'Refine by'; - // Extracts the current entity active in the browse view - const { browseEntity: { currentEntity = '' } = {} } = state; - // Retrieves properties for the current entity from the state tree - const { browseEntity: { [currentEntity]: { query: { urn, name } } } } = state; - // Removes `s` from the end of each entity name. Ember routes for individual entities are singular, and also - // returned entities contain id prop that is the singular type name, suffixed with Id, e.g. metricId - // datasets -> dataset, metrics -> metric, flows -> flow - const singularName = currentEntity.slice(0, -1); - const query = - { - datasets: urn, - metrics: name, - flows: urn - }[currentEntity] || null; - let nodes = getNodes(currentEntity, state)(query) || []; - /** - * Creates dynamic query link params for each node - * @type {Array} list of child nodes or datasets to render - */ - nodes = nodes.map(({ nodeName, nodeUrl, [`${singularName}Id`]: id, application = '' }) => { - nodeUrl = String(nodeUrl); - const node = { - title: nodeName, - text: nodeName - }; - - // If the id prop (datasetId|metricId|flowId) is truthy, then it is a standalone entity - if (id) { - let idNode = Object.assign({}, node); - - if (singularName === 'flow' && application) { - idNode = Object.assign({}, idNode, { - queryParams: { - name: application - } - }); - } - - return Object.assign({}, idNode, { - route: `${currentEntity}.${singularName}`, - model: id - }); - } - - return Object.assign({}, node, { - route: `browse.entity`, - model: currentEntity, - queryParams: nodeUrlToQueryParams(nodeUrl) - }); - }); - - return { nodes, header }; -}; -export default connect(stateToComputed)(Component.extend({})); diff --git a/wherehows-web/app/components/browser/containers/browser-rail.ts b/wherehows-web/app/components/browser/containers/browser-rail.ts new file mode 100644 index 0000000000..72291a5f2c --- /dev/null +++ b/wherehows-web/app/components/browser/containers/browser-rail.ts @@ -0,0 +1,77 @@ +import Component from '@ember/component'; +import { get, set } from '@ember/object'; +import { task } from 'ember-concurrency'; +import { nodeToQueryParams } from 'wherehows-web/constants'; +import { IBrowserRouteParams } from 'wherehows-web/routes/browse/entity'; +import { readPlatforms } from 'wherehows-web/utils/api/platforms/platform'; +import { arrayMap } from 'wherehows-web/utils/array'; +import { IReadDatasetsOptionBag } from 'wherehows-web/typings/api/datasets/dataset'; + +/** + * Describes a node with parameters used by dynamic-link component to create links to items lised in the rail + * @interface IRailNode + */ +interface IRailNode { + title: string; + text: string; + route: 'browse.entity'; + model: IBrowserRouteParams['entity']; + queryParams: Partial; +} + +/** + * Given a platform and entity, returns a closure function that maps each node to a + * list of IRailNode + * @param {string} platform + * @param {IBrowserRouteParams['entity']} entity + * @returns {(array: string[]) => IRailNode[]} + */ +export const mapNodeToRoute = ( + platform: string, + entity: IBrowserRouteParams['entity'] +): ((array: string[]) => IRailNode[]) => + arrayMap((node: string): IRailNode => ({ + title: node, + text: node, + route: 'browse.entity', + model: entity, + queryParams: nodeToQueryParams({ platform, node }) + })); + +export default class BrowserRail extends Component { + /** + * Passed in parameters containing route or queryparameters values to be used in request + * @type {IBrowserRouteParams} + * @memberof BrowserRail + */ + params: IBrowserRouteParams; + + /** + * Maintains a list the nodes platforms or prefixes available in the selected entity + * @type {Array} + * @memberof BrowserRail + */ + nodes: Array = []; + + didUpdateAttrs() { + this._super(...arguments); + get(this, 'getNodesTask').perform(); + } + + didInsertElement() { + this._super(...arguments); + get(this, 'getNodesTask').perform(); + } + + /** + * Gets the nodes: platforms, or prefixes for the selected entity + * @type {TaskProperty> & {perform: (a?: {} | undefined) => TaskInstance>}} + * @memberof BrowserRail + */ + getNodesTask = task(function*(this: BrowserRail): IterableIterator>> { + const { prefix, platform, entity } = get(this, 'params'); + const nodes: Array = mapNodeToRoute(platform, entity)(yield readPlatforms({ platform, prefix })); + + set(this, 'nodes', nodes); + }).drop(); +} diff --git a/wherehows-web/app/components/browser/containers/browser-summary.ts b/wherehows-web/app/components/browser/containers/browser-summary.ts new file mode 100644 index 0000000000..b0a084c8f8 --- /dev/null +++ b/wherehows-web/app/components/browser/containers/browser-summary.ts @@ -0,0 +1,86 @@ +import Component from '@ember/component'; +import { get, setProperties } from '@ember/object'; +import { task, TaskInstance } from 'ember-concurrency'; +import { IBrowserRouteParams } from 'wherehows-web/routes/browse/entity'; +import { readDatasetsCount } from 'wherehows-web/utils/api/datasets/dataset'; + +// Describes the index signature for the metadata object used in the browser summary component +type IBrowserMetadata = { + [K in IBrowserRouteParams['entity']]: { + count: number; + currentPlatform: string; + } +}; + +// Describes the index signature for the strategy pattern in the getCountsTask +type ICountTaskStrategy = { [K in IBrowserRouteParams['entity']]: TaskInstance> }; + +export default class BrowserSummary extends Component { + /** + * Passed in parameters containing route or query parameters values to be used in request + * @type {IBrowserRouteParams} + * @memberof BrowserSummary + */ + params: IBrowserRouteParams; + + /** + * Lists the types of entities supported + * @type {ReadonlyArray} + * @memberof BrowserSummary + */ + entities: ReadonlyArray = ['datasets', 'metrics', 'flows']; + + /** + * Contains the properties for each entity to displayed UI metadata + * @type {IBrowserMetadata} + * @memberof BrowserSummary + */ + metadata: IBrowserMetadata = this.entities.reduce( + (metadata, entity) => ({ + ...metadata, + [entity]: { count: 0, currentPlatform: '' } + }), + {} + ); + + didUpdateAttrs() { + this._super(...arguments); + get(this, 'getCountsTask').perform(); + } + + didInsertElement() { + this._super(...arguments); + get(this, 'getCountsTask').perform(); + } + + /** + * Parent task to retrieve counts for each IBrowserParams entity type as needed + * @type {TaskProperty>> & {perform: (a?: {} | undefined) => TaskInstance>>}} + * @memberof BrowserSummary + */ + getCountsTask = task(function*(this: BrowserSummary): IterableIterator>> { + const { prefix, platform, entity } = get(this, 'params'); + + return ({ + datasets: yield get(this, 'getDatasetsCountTask').perform(platform, prefix) + })[entity]; + }); + + /** + * Gets and sets the dataset count + * @type {TaskProperty> & {perform: (a1: string, a2: string) => TaskInstance>}} + * @memberof BrowserSummary + */ + getDatasetsCountTask = task(function*( + this: BrowserSummary, + platform: string, + prefix: string + ): IterableIterator> { + const entityMetadata = get(get(this, 'metadata'), 'datasets'); + + setProperties(entityMetadata, { + count: yield readDatasetsCount({ prefix, platform }), + currentPlatform: platform + }); + }); +} diff --git a/wherehows-web/app/components/browser/containers/browser-view.js b/wherehows-web/app/components/browser/containers/browser-view.js deleted file mode 100644 index 0ace0f70db..0000000000 --- a/wherehows-web/app/components/browser/containers/browser-view.js +++ /dev/null @@ -1,19 +0,0 @@ -import Component from '@ember/component'; -import { connect } from 'ember-redux'; - -const entities = ['datasets', 'metrics', 'flows']; // hardcoded here to maintain the sort order -/** - * Selector function that takes a Redux Store to extract - * state props for the browser-view - * @param {Object} browseData - * @return {Object} - */ -const stateToComputed = ({ browse: { browseData = {} } = {} }) => ({ - browseData: entities.map(browseDatum => - Object.assign( - { entity: browseDatum }, // assigns key name to resulting object - browseData[browseDatum] - ) - ) -}); -export default connect(stateToComputed)(Component.extend({})); diff --git a/wherehows-web/app/components/browser/containers/browser-viewport.js b/wherehows-web/app/components/browser/containers/browser-viewport.js deleted file mode 100644 index 9f4a187abe..0000000000 --- a/wherehows-web/app/components/browser/containers/browser-viewport.js +++ /dev/null @@ -1,59 +0,0 @@ -import Component from '@ember/component'; -import { connect } from 'ember-redux'; - -/** - * Extract the childIds for an entity under a specific category - * @param entity - * @param state - */ -const getChildIds = (entity, state = {}) => query => - ({ - get datasets() { - return state[entity].byUrn[query]; - }, - get metrics() { - return state[entity].byName[query]; - }, - get flows() { - // Flows are retrieved by name as well - return this.datasets; - } - }[entity]); - -/** - * Selector function that takes a Redux Store to extract - * state props for the browser-rail - * @return {Object} mapping of computed props to state - */ -const stateToComputed = state => { - // Extracts the current entity active in the browse view - const { browseEntity: { currentEntity = '' } = {} } = state; - // Retrieves properties for the current entity from the state tree - const { browseEntity: { [currentEntity]: { query: { urn, name } } } } = state; - // Default urn to null, which represents the top-level parent - - const query = - { - datasets: urn, - metrics: name, - flows: urn - }[currentEntity] || null; - // Read the list of ids child entity ids associated with the urn - const childIds = getChildIds(currentEntity, state)(query) || []; - // Read the list of entities, stored in the byId property - const { [currentEntity]: { byId: entities } } = state; - /** - * Takes the currentEntity which is plural and strips the trailing `s` and appends - * the entity sub route - * @type {string} - */ - const entityRoute = `${currentEntity}.${currentEntity.slice(0, -1)}`; - - return { - currentEntity, - entityRoute, - entities: childIds.map(id => entities[id]) // Extract out the intersection of childIds from the entity map - }; -}; - -export default connect(stateToComputed)(Component.extend({})); diff --git a/wherehows-web/app/components/browser/containers/browser-viewport.ts b/wherehows-web/app/components/browser/containers/browser-viewport.ts new file mode 100644 index 0000000000..aa5e11ea94 --- /dev/null +++ b/wherehows-web/app/components/browser/containers/browser-viewport.ts @@ -0,0 +1,69 @@ +import Component from '@ember/component'; +import { get, setProperties } from '@ember/object'; +import { task } from 'ember-concurrency'; +import { IBrowserRouteParams } from 'wherehows-web/routes/browse/entity'; +import { IDatasetView } from 'wherehows-web/typings/api/datasets/dataset'; +import { readDatasets } from 'wherehows-web/utils/api/datasets/dataset'; + +// Describes the index signature for strategy pattern in the getEntitiesTask +type IGetEntityTaskStrategy = { [K in IBrowserRouteParams['entity']]: Promise> }; + +export default class BrowserViewport extends Component { + /** + * Passed in parameters containing route or queryparameters values to be used in request + * @type {IBrowserRouteParams} + * @memberof BrowserViewport + */ + params: IBrowserRouteParams; + + /** + * Initial value for the entity being viewed + * @memberof BrowserViewport + */ + currentEntity: IBrowserRouteParams['entity'] = 'datasets'; + + /** + * Ember route for the entities being rendered + * @type {string} + * @memberof BrowserViewport + */ + entityRoute = ''; + + /** + * List of entities to be rendered in view + * @type {Array} + * @memberof BrowserViewport + */ + entities: Array = []; + + didUpdateAttrs() { + this._super(...arguments); + get(this, 'getEntitiesTask').perform(); + } + + didInsertElement() { + this._super(...arguments); + get(this, 'getEntitiesTask').perform(); + } + + /** + * Async requests for the list of entities and sets the value on class + * @type {TaskProperty> & {perform: (a?: {} | undefined) => TaskInstance>}} + * @memberof BrowserViewport + */ + getEntitiesTask = task(function*(this: BrowserViewport): IterableIterator>> { + const { prefix, platform, entity } = get(this, 'params'); + + const entities = ({ + datasets: readDatasets({ platform, prefix }), + metrics: Promise.resolve([]), + flows: Promise.resolve([]) + })[entity]; + + setProperties(this, { + entities: yield entities, + currentEntity: entity, + entityRoute: `${entity}.${entity.slice(0, -1)}` + }); + }); +} diff --git a/wherehows-web/app/components/dataset-authors.ts b/wherehows-web/app/components/dataset-authors.ts index 21bd9ead58..3da3058e12 100644 --- a/wherehows-web/app/components/dataset-authors.ts +++ b/wherehows-web/app/components/dataset-authors.ts @@ -3,7 +3,7 @@ import { inject } from '@ember/service'; import ComputedProperty, { filter } from '@ember/object/computed'; import { set, get, computed, getProperties } from '@ember/object'; import { assert } from '@ember/debug'; -import { task, Task } from 'ember-concurrency'; +import { task } from 'ember-concurrency'; import UserLookup from 'wherehows-web/services/user-lookup'; import CurrentUser from 'wherehows-web/services/current-user'; @@ -138,9 +138,7 @@ export default class DatasetAuthors extends Component { * @type {Task>, void>} * @memberof DatasetAuthors */ - saveOwners: Task>, void> = task(function*( - this: DatasetAuthors - ): IterableIterator>> { + saveOwners = task(function*(this: DatasetAuthors): IterableIterator>> { yield get(this, 'save')(get(this, 'owners')); }).drop(); diff --git a/wherehows-web/app/components/dataset-compliance.ts b/wherehows-web/app/components/dataset-compliance.ts index adc4cb4909..52faca0b5b 100644 --- a/wherehows-web/app/components/dataset-compliance.ts +++ b/wherehows-web/app/components/dataset-compliance.ts @@ -406,7 +406,6 @@ export default class DatasetCompliance extends ObservableDecorator { } didInsertElement(this: DatasetCompliance) { - // @ts-ignore ts limitation with the ember object model, fixed in ember 3.1 with es5 getters get(this, 'complianceAvailabilityTask').perform(); } @@ -428,7 +427,6 @@ export default class DatasetCompliance extends ObservableDecorator { complianceAvailabilityTask = task(function*( this: DatasetCompliance ): IterableIterator>>> { - // @ts-ignore ts limitation with the ember object model, fixed in ember 3.1 with es5 getters yield get(this, 'getPlatformPoliciesTask').perform(); const supportedPurgePolicies = get(this, 'supportedPurgePolicies'); diff --git a/wherehows-web/app/components/datasets/dataset-actions.js b/wherehows-web/app/components/datasets/dataset-actions.ts similarity index 50% rename from wherehows-web/app/components/datasets/dataset-actions.js rename to wherehows-web/app/components/datasets/dataset-actions.ts index 5d2c6d3c6c..684c07a537 100644 --- a/wherehows-web/app/components/datasets/dataset-actions.js +++ b/wherehows-web/app/components/datasets/dataset-actions.ts @@ -1,2 +1,2 @@ import Component from '@ember/component'; -export default Component.extend({}); +export default class extends Component {} diff --git a/wherehows-web/app/components/search/filter-rail.js b/wherehows-web/app/components/search/filter-rail.js deleted file mode 100644 index 04aafe6ea7..0000000000 --- a/wherehows-web/app/components/search/filter-rail.js +++ /dev/null @@ -1,12 +0,0 @@ -import Component from '@ember/component'; - -export default Component.extend({ - tagName: 'ul', - classNames: ['nacho-filter-rail'], - - checkboxComponent: 'search/checkbox-group', - radioGroupComponent: 'search/radio-group', - dropdownComponent: 'search/dropdown-selection', - dateRangeComponent: 'search/daterange-selection', - linksComponent: 'search/link-group' -}); diff --git a/wherehows-web/app/components/search/filter-rail.ts b/wherehows-web/app/components/search/filter-rail.ts new file mode 100644 index 0000000000..fa890013a8 --- /dev/null +++ b/wherehows-web/app/components/search/filter-rail.ts @@ -0,0 +1,17 @@ +import Component from '@ember/component'; + +export default class extends Component { + tagName = 'ul'; + + classNames = ['nacho-filter-rail']; + + checkboxComponent = 'search/checkbox-group'; + + radioGroupComponent = 'search/radio-group'; + + dropdownComponent = 'search/dropdown-selection'; + + dateRangeComponent = 'search/daterange-selection'; + + linksComponent = 'search/link-group'; +} diff --git a/wherehows-web/app/components/search/link-group.js b/wherehows-web/app/components/search/link-group.js deleted file mode 100644 index 9a6f632269..0000000000 --- a/wherehows-web/app/components/search/link-group.js +++ /dev/null @@ -1,7 +0,0 @@ -import Component from '@ember/component'; - -export default Component.extend({ - tagName: 'li', - - classNames: ['nacho-filter-card'] -}); diff --git a/wherehows-web/app/components/search/link-group.ts b/wherehows-web/app/components/search/link-group.ts new file mode 100644 index 0000000000..84a7b61aac --- /dev/null +++ b/wherehows-web/app/components/search/link-group.ts @@ -0,0 +1,7 @@ +import Component from '@ember/component'; + +export default class extends Component { + tagName = 'li'; + + classNames = ['nacho-filter-card']; +} diff --git a/wherehows-web/app/constants/datasets/platform.ts b/wherehows-web/app/constants/datasets/platform.ts index 7cffde4a4e..cd45fe1097 100644 --- a/wherehows-web/app/constants/datasets/platform.ts +++ b/wherehows-web/app/constants/datasets/platform.ts @@ -1,9 +1,15 @@ +import { IBrowserRouteParams } from 'wherehows-web/routes/browse/entity'; +import { IReadDatasetsOptionBag } from 'wherehows-web/typings/api/datasets/dataset'; +import { isDatasetPlatform, isDatasetPrefix } from 'wherehows-web/utils/validators/platform'; + /** * The known/supported list of dataset platforms * @enum {string} */ enum DatasetPlatform { Kafka = 'kafka', + KafkaLc = 'kafka-lc', + Presto = 'presto', Espresso = 'espresso', Oracle = 'oracle', MySql = 'mysql', @@ -13,7 +19,39 @@ enum DatasetPlatform { Couchbase = 'couchbase', Voldemort = 'voldemort', Venice = 'venice', - Hive = 'hive' + Vector = 'vector', + Hive = 'hive', + FollowFeed = 'followfeed' } -export { DatasetPlatform }; +/** + * Given a platform and a new node, composes an object of query parameters to be used in the request for + * platforms or datasets + * @param {(Pick & { node: string })} { platform, node } + * @returns {Partial} + */ +const nodeToQueryParams = ({ + platform, + node +}: Pick & { node: string }): Partial => { + const queryParams = {}; + + if (isDatasetPrefix(node)) { + Object.assign(queryParams, { prefix: node.replace(/\//g, '') }); + } + + // If the node is a platform, assign that value to the query params object + if (isDatasetPlatform(node)) { + Object.assign(queryParams, { platform: node }); + } + + // If a platform value is already present override the previously set value + // there should not be a 'future' platform value if one already exists + if (platform) { + Object.assign(queryParams, { platform }); + } + + return queryParams; +}; + +export { DatasetPlatform, nodeToQueryParams }; diff --git a/wherehows-web/app/controllers/browse/entity.js b/wherehows-web/app/controllers/browse/entity.js deleted file mode 100644 index 5ed6d06e69..0000000000 --- a/wherehows-web/app/controllers/browse/entity.js +++ /dev/null @@ -1,12 +0,0 @@ -import Controller from '@ember/controller'; - -/** - * Handles query params for browse.entity route - */ -export default Controller.extend({ - queryParams: ['page', 'urn', 'size', 'name'], - page: 1, - urn: '', - name: '', - size: 10 -}); diff --git a/wherehows-web/app/controllers/browse/entity.ts b/wherehows-web/app/controllers/browse/entity.ts new file mode 100644 index 0000000000..e8d1e25291 --- /dev/null +++ b/wherehows-web/app/controllers/browse/entity.ts @@ -0,0 +1,19 @@ +import Controller from '@ember/controller'; +import { IBrowserRouteParams } from 'wherehows-web/routes/browse/entity'; + +/** + * Handles query params for browse.entity route + */ +export default class extends Controller implements IBrowserRouteParams { + queryParams = ['page', 'prefix', 'size', 'platform']; + + entity: IBrowserRouteParams['entity']; + + page = 1; + + prefix = ''; + + platform = ''; + + size = 10; +} diff --git a/wherehows-web/app/routes/browse.js b/wherehows-web/app/routes/browse.js deleted file mode 100644 index b57181ec43..0000000000 --- a/wherehows-web/app/routes/browse.js +++ /dev/null @@ -1,30 +0,0 @@ -import Route from '@ember/routing/route'; -import route from 'ember-redux/route'; -import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; -import { asyncRequestBrowseData } from 'wherehows-web/actions/browse'; - -// TODO: DSS-6581 Create URL retrieval module -// TODO: Route should transition to browse/entity, pay attention to the fact that -// this route initializes store with entity metrics on entry -const entityUrls = { - datasets: '/api/v1/datasets', - metrics: '/api/v1/metrics', - flows: '/api/v1/flows' -}; - -export default route({ - model: (dispatch, { entity = 'datasets', page = '1' }) => dispatch(asyncRequestBrowseData(page, entity, entityUrls)) -})( - Route.extend(AuthenticatedRouteMixin, { - /** - * Browse route does not render any content, but hydrates the store with initial data transition to child route - * @param {Object} model result from model call - * @param {Ember.Transition} transition - */ - afterModel(model, transition) { - // Extract the entity being viewed from the transition state - const { params: { 'browse.entity': { entity = 'datasets' } = {} } } = transition; - this.transitionTo('browse.entity', entity); - } - }) -); diff --git a/wherehows-web/app/routes/browse.ts b/wherehows-web/app/routes/browse.ts new file mode 100644 index 0000000000..8a684bb337 --- /dev/null +++ b/wherehows-web/app/routes/browse.ts @@ -0,0 +1,13 @@ +import Route from '@ember/routing/route'; +import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; +import Ember from 'ember'; // type import, no emit + +export default class extends Route.extend(AuthenticatedRouteMixin) { + afterModel(_model: any, transition: Ember.Transition & { params: any }) { + // Extract the entity being viewed from the transition state + const { params: { 'browse.entity': { entity = 'datasets' } = {} } } = transition; + + // transition to entity specific sub route + this.transitionTo('browse.entity', entity); + } +} diff --git a/wherehows-web/app/routes/browse/entity.js b/wherehows-web/app/routes/browse/entity.js deleted file mode 100644 index cf1da3a5d1..0000000000 --- a/wherehows-web/app/routes/browse/entity.js +++ /dev/null @@ -1,36 +0,0 @@ -import Route from '@ember/routing/route'; -import route from 'ember-redux/route'; -import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; -import { asyncRequestEntityQueryData } from 'wherehows-web/actions/browse/entity'; - -// TODO: DSS-6581 Create URL retrieval module -const listUrl = '/api/v1/list'; -const queryParamsKeys = ['page', 'urn', 'name']; - -/** - * Creates a route handler for browse.entity route - * entity can be any (datasets|metrics|flows) - * @type {Ember.Route} - */ -const BrowseEntityRoute = Route.extend(AuthenticatedRouteMixin, { - queryParams: queryParamsKeys.reduce( - (queryParams, param) => - Object.assign({}, queryParams, { - [param]: { refreshModel: true } - }), - {} - ) -}); - -/** - * Ember Redux decorator wraps incoming route object and injects the redux store.dispatch method as the - * first argument - */ -export default route({ - /** - * - * @param dispatch - * @param params - */ - model: (dispatch, params) => dispatch(asyncRequestEntityQueryData(params, listUrl, { queryParamsKeys })) -})(BrowseEntityRoute); diff --git a/wherehows-web/app/routes/browse/entity.ts b/wherehows-web/app/routes/browse/entity.ts new file mode 100644 index 0000000000..6b0556d837 --- /dev/null +++ b/wherehows-web/app/routes/browse/entity.ts @@ -0,0 +1,35 @@ +import Route from '@ember/routing/route'; +import { set } from '@ember/object'; +import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; +import { refreshModelQueryParams } from 'wherehows-web/utils/helpers/routes'; +import EntityController from 'wherehows-web/controllers/browse/entity'; + +const queryParamsKeys = ['page', 'prefix', 'platform', 'size']; + +/** + * Describes the route parameter interface for the browser route + * @export + * @interface IBrowserRouteParams + */ +export interface IBrowserRouteParams { + entity: 'datasets' | 'metrics' | 'flows'; + page: number; + size: number; + platform: string; + prefix: string; +} + +export default class extends Route.extend(AuthenticatedRouteMixin) { + queryParams = refreshModelQueryParams(queryParamsKeys); + + setupController(controller: EntityController, { entity }: IBrowserRouteParams) { + this._super(...arguments); + // sets the entity property on the controller + set(controller, 'entity', entity); + } + + model(params: IBrowserRouteParams): IBrowserRouteParams { + const { entity, platform, page, size, prefix } = params; + return { entity, platform, page, size, prefix }; + } +} diff --git a/wherehows-web/app/routes/datasets.ts b/wherehows-web/app/routes/datasets.ts index a5f4fc560e..dbce166698 100644 --- a/wherehows-web/app/routes/datasets.ts +++ b/wherehows-web/app/routes/datasets.ts @@ -1,6 +1,4 @@ -import Ember from 'ember'; +import Route from '@ember/routing/route'; import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; -const { Route } = Ember; - -export default Route.extend(AuthenticatedRouteMixin); +export default class extends Route.extend(AuthenticatedRouteMixin) {} diff --git a/wherehows-web/app/routes/datasets/index.ts b/wherehows-web/app/routes/datasets/index.ts index 4c37d233da..a6bc3cd02d 100644 --- a/wherehows-web/app/routes/datasets/index.ts +++ b/wherehows-web/app/routes/datasets/index.ts @@ -1,9 +1,8 @@ -import Ember from 'ember'; +import Route from '@ember/routing/route'; -const { Route } = Ember; - -export default Route.extend({ +export default class extends Route { redirect() { - this.transitionTo('browse.entity', 'datasets'); + // default transition to datasets route if user enters through index + return this.transitionTo('browse.entity', 'datasets'); } -}); +} diff --git a/wherehows-web/app/styles/components/browse-nav/_browse-nav.scss b/wherehows-web/app/styles/components/browse-nav/_browse-nav.scss index 5568214693..57c55a5928 100644 --- a/wherehows-web/app/styles/components/browse-nav/_browse-nav.scss +++ b/wherehows-web/app/styles/components/browse-nav/_browse-nav.scss @@ -7,11 +7,28 @@ $browse-nav-border-color: set-color(grey, light); &__item { @include flex-column-center; + opacity: 1; + transition: opacity 0.2s ease-in-out; + } + + &__alt { + @include flex-column-center; + opacity: 0; + transition: opacity 0.2s ease-in-out; + font-weight: fw(normal, 6); + font-size: 24px; + position: absolute; + margin: 0; + top: 0; + left: 0; + right: 0; + bottom: 0; } &__entity { cursor: pointer; border-bottom: 1px solid $browse-nav-border-color; + min-height: 90px; /** * Overrides bootstrap .active class. * Ember applies this for active route links @@ -23,9 +40,24 @@ $browse-nav-border-color: set-color(grey, light); /** * Resets the bootstrap grid margins */ - &[class|="col"]{ + &[class|='col'] { padding: 0; } + + &#{&} { + display: flex; + justify-content: center; + } + + &:hover { + .browse-nav__item { + opacity: 0; + } + + .browse-nav__alt { + opacity: 1; + } + } } &__entity + &__entity { @@ -43,4 +75,21 @@ $browse-nav-border-color: set-color(grey, light); text-transform: capitalize; margin: 0; } + + &__subtext { + font-weight: fw(normal, 2); + margin: 0; + } +} + +.browse-viewport { + &__loading { + $loading-container-height: 170px; + height: $loading-container-height; + width: 95%; + } +} + +.nav-link-to { + width: 100%; } diff --git a/wherehows-web/app/styles/components/nacho/_nacho-filter-rail.scss b/wherehows-web/app/styles/components/nacho/_nacho-filter-rail.scss index 1a2980e840..eac9f74d6c 100644 --- a/wherehows-web/app/styles/components/nacho/_nacho-filter-rail.scss +++ b/wherehows-web/app/styles/components/nacho/_nacho-filter-rail.scss @@ -1,4 +1,4 @@ -@import "restyle"; +@import 'restyle'; $pad-width: 16px; @@ -19,6 +19,11 @@ $pad-width: 16px; .nacho-filter-rail { @include restyle(filter-rail); + &__loading { + $loading-container-height: 150px; + height: $loading-container-height; + } + &__header { color: set-color(blue, oxford); background-color: set-color(white, base); @@ -37,7 +42,8 @@ $pad-width: 16px; border-bottom: none; } - &__label, .ember-radio-button { + &__label, + .ember-radio-button { display: block; } @@ -58,7 +64,7 @@ $pad-width: 16px; white-space: nowrap; &::before { - content: "+"; + content: '+'; color: set-color(grey, mid); padding: 0 10px 0 10px; } @@ -83,7 +89,7 @@ $pad-width: 16px; &:before, &:after { - content: " "; + content: ' '; height: 20px; width: 20px; position: absolute; @@ -108,7 +114,7 @@ $pad-width: 16px; border-width: 6px; } - input[type=radio] { + input[type='radio'] { opacity: 0; margin: 16px 2px 0 0; position: absolute; diff --git a/wherehows-web/app/styles/components/pendulum-ellipsis-animation/_ellipsis-loader.scss b/wherehows-web/app/styles/components/pendulum-ellipsis-animation/_ellipsis-loader.scss index 87a58c748b..b27e3ebac8 100644 --- a/wherehows-web/app/styles/components/pendulum-ellipsis-animation/_ellipsis-loader.scss +++ b/wherehows-web/app/styles/components/pendulum-ellipsis-animation/_ellipsis-loader.scss @@ -1,14 +1,17 @@ .ellipsis-loader { - display: block; - min-width: 70px; + $container-width: 70px; + min-width: $container-width; + height: 100%; &__ellipsis { + $ellipsis-height: 15px; margin-left: -25px; position: relative; display: flex; align-items: center; justify-content: left; - height: 15px; + min-height: $ellipsis-height; + height: 100%; } &__circle { diff --git a/wherehows-web/app/templates/browse/entity.hbs b/wherehows-web/app/templates/browse/entity.hbs index b17011a273..f1b0b7d006 100644 --- a/wherehows-web/app/templates/browse/entity.hbs +++ b/wherehows-web/app/templates/browse/entity.hbs @@ -1,15 +1,14 @@
- {{!-- TODO: DSS-7029 browser-view is really a misnomer and should be renamed to browser-summary :( --}} - {{browser/containers/browser-view}} + {{browser/containers/browser-summary params=model}}
- {{browser/containers/browser-rail header=header}} + {{browser/containers/browser-rail header=header params=model}}
- {{browser/containers/browser-viewport}} + {{browser/containers/browser-viewport params=model}}
{{outlet}} diff --git a/wherehows-web/app/templates/components/browser/browser-rail.hbs b/wherehows-web/app/templates/components/browser/browser-rail.hbs index 6d912f8d40..2402068380 100644 --- a/wherehows-web/app/templates/components/browser/browser-rail.hbs +++ b/wherehows-web/app/templates/components/browser/browser-rail.hbs @@ -1,6 +1,16 @@ {{#search/filter-rail header=header as |rail|}} - {{rail.links - links=nodes}} + + {{#if nodesTask.isRunning}} +
+ {{pendulum-ellipsis-animation}} +
+ {{else}} + + {{rail.links + links=nodes}} + + {{/if}} + {{/search/filter-rail}} {{yield}} diff --git a/wherehows-web/app/templates/components/browser/browser-summary.hbs b/wherehows-web/app/templates/components/browser/browser-summary.hbs new file mode 100644 index 0000000000..2ef9b78ef7 --- /dev/null +++ b/wherehows-web/app/templates/components/browser/browser-summary.hbs @@ -0,0 +1,29 @@ + diff --git a/wherehows-web/app/templates/components/browser/browser-view.hbs b/wherehows-web/app/templates/components/browser/browser-view.hbs deleted file mode 100644 index fe96ccf92c..0000000000 --- a/wherehows-web/app/templates/components/browser/browser-view.hbs +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/wherehows-web/app/templates/components/browser/browser-viewport.hbs b/wherehows-web/app/templates/components/browser/browser-viewport.hbs index 760b910e3b..d8f3291e29 100644 --- a/wherehows-web/app/templates/components/browser/browser-viewport.hbs +++ b/wherehows-web/app/templates/components/browser/browser-viewport.hbs @@ -61,42 +61,49 @@ {{/table.body}} {{/dataset-table}} {{else}} - {{#dataset-table - fields=entities as |table|}} - {{#table.body as |body|}} - {{#each - table.data as |entity|}} - {{#body.row as |row|}} - {{#row.cell}} - {{#link-to entityRoute entity.id}} - - {{entity.name}} - - {{/link-to}} -
- {{#if (eq currentEntity 'metrics')}} - {{entity.group}} - {{entity.dashboardName}} + {{#if entities}} + + {{#dataset-table + fields=entities as |table|}} + {{#table.body as |body|}} + {{#each + table.data as |entity|}} + {{#body.row as |row|}} + {{#row.cell}} + {{#link-to entityRoute 'urn' (query-params urn=entity.uri)}} + + {{entity.nativeName}} + + {{/link-to}}
- {{entity.description}} - {{/if}} + {{#if (eq currentEntity 'metrics')}} + {{entity.group}} - {{entity.dashboardName}} +
+ {{entity.description}} + {{/if}} - {{#if (eq currentEntity 'datasets')}} - {{dataset-owner-list owners=entity.owners datasetName=entity.name}} - {{/if}} + {{#if entity.modifiedTime}} + Last Modified: - {{#if entity.formatedModified}} - Last Modified: + + {{moment-from-now entity.modifiedTime }} + + {{/if}} + {{/row.cell}} + {{#row.cell}} + {{datasets/dataset-actions actionItems=actionItems}} + {{/row.cell}} + {{/body.row}} + {{/each}} + {{/table.body}} + {{/dataset-table}} - - {{moment-from-now entity.formatedModified }} - - {{/if}} - {{/row.cell}} - {{#row.cell}} - {{datasets/dataset-actions actionItems=actionItems}} - {{/row.cell}} - {{/body.row}} - {{/each}} - {{/table.body}} - {{/dataset-table}} + {{else}} + + {{empty-state + heading="No entities found" + subHead="Could not find any datasets in our records" + }} + + {{/if}} {{/if}} diff --git a/wherehows-web/app/templates/components/browser/containers/browser-rail.hbs b/wherehows-web/app/templates/components/browser/containers/browser-rail.hbs index b620af727b..3f0b8f9aa6 100644 --- a/wherehows-web/app/templates/components/browser/containers/browser-rail.hbs +++ b/wherehows-web/app/templates/components/browser/containers/browser-rail.hbs @@ -1 +1 @@ -{{browser/browser-rail nodes=nodes}} +{{browser/browser-rail nodes=nodes nodesTask=getNodesTask}} diff --git a/wherehows-web/app/templates/components/browser/containers/browser-summary.hbs b/wherehows-web/app/templates/components/browser/containers/browser-summary.hbs new file mode 100644 index 0000000000..7a5ef31808 --- /dev/null +++ b/wherehows-web/app/templates/components/browser/containers/browser-summary.hbs @@ -0,0 +1 @@ +{{browser/browser-summary metadata=metadata datasetTask=getDatasetsCountTask}} diff --git a/wherehows-web/app/templates/components/browser/containers/browser-view.hbs b/wherehows-web/app/templates/components/browser/containers/browser-view.hbs deleted file mode 100644 index 2fa3831dc5..0000000000 --- a/wherehows-web/app/templates/components/browser/containers/browser-view.hbs +++ /dev/null @@ -1 +0,0 @@ -{{browser/browser-view browseData=browseData}} diff --git a/wherehows-web/app/templates/components/browser/containers/browser-viewport.hbs b/wherehows-web/app/templates/components/browser/containers/browser-viewport.hbs index b0eb1dfd6e..b352112299 100644 --- a/wherehows-web/app/templates/components/browser/containers/browser-viewport.hbs +++ b/wherehows-web/app/templates/components/browser/containers/browser-viewport.hbs @@ -1 +1,18 @@ -{{browser/browser-viewport currentEntity=currentEntity entities=entities entityRoute=entityRoute}} +{{#if getEntitiesTask.isRunning}} + +
+ {{pendulum-ellipsis-animation}} +
+ +{{else}} + {{#if getEntitiesTask.last.isError}} + + {{empty-state + heading="An error occurred getting entities" + subHead=getEntitiesTask.last.error + }} + + {{else}} + {{browser/browser-viewport currentEntity=currentEntity entities=entities entityRoute=entityRoute}} + {{/if}} +{{/if}} diff --git a/wherehows-web/app/templates/components/datasets/dataset-actions.hbs b/wherehows-web/app/templates/components/datasets/dataset-actions.hbs index 825bdfa7a3..b832aed5f4 100644 --- a/wherehows-web/app/templates/components/datasets/dataset-actions.hbs +++ b/wherehows-web/app/templates/components/datasets/dataset-actions.hbs @@ -1,29 +1,6 @@ -{{!-- rename to entity-actions --}}
    {{#each actionItems as |actionItem|}} - {{!--maybe dynamic link with actionItem only for each?--}}
  • {{/each}}
- + diff --git a/wherehows-web/app/templates/components/nav-link.hbs b/wherehows-web/app/templates/components/nav-link.hbs index 7ab5c60a44..49e61c440e 100644 --- a/wherehows-web/app/templates/components/nav-link.hbs +++ b/wherehows-web/app/templates/components/nav-link.hbs @@ -1,2 +1,2 @@ {{!-- https://github.com/zoltan-nz/ember-bootstrap-nav-link/blob/master/addon/components/nav-link-to.js --}} -{{yield}} \ No newline at end of file +{{yield}} diff --git a/wherehows-web/app/typings/api/datasets/dataset.d.ts b/wherehows-web/app/typings/api/datasets/dataset.d.ts index b6bc7ff467..57b140fc09 100644 --- a/wherehows-web/app/typings/api/datasets/dataset.d.ts +++ b/wherehows-web/app/typings/api/datasets/dataset.d.ts @@ -57,7 +57,7 @@ interface IDatasetGetResponse { } /** - * Describes the interface of a response from the GET datasetView endpoint + * Describes the interface of a response from the GET datasetView endpoint * @interface IDatasetViewGetResponse */ interface IDatasetViewGetResponse { @@ -65,4 +65,31 @@ interface IDatasetViewGetResponse { dataset?: IDatasetView; } -export { IDatasetViewGetResponse, IDatasetView, IDatasetGetResponse, IDataset }; +/** + * Describes the response from the GET /datasets api + * @interface IDatasetsGetResponse + */ +interface IDatasetsGetResponse { + total: number; + start: number; + count: number; + elements: Array; +} + +/** + * Describes the options for the dataset + * @interface IReadDatasetsOptionBag + */ +interface IReadDatasetsOptionBag { + platform: DatasetPlatform | string; + prefix: string; +} + +export { + IDatasetViewGetResponse, + IDatasetView, + IDatasetGetResponse, + IDataset, + IDatasetsGetResponse, + IReadDatasetsOptionBag +}; diff --git a/wherehows-web/app/typings/ember-concurrency.d.ts b/wherehows-web/app/typings/ember-concurrency.d.ts index 9785003653..8521bbb310 100644 --- a/wherehows-web/app/typings/ember-concurrency.d.ts +++ b/wherehows-web/app/typings/ember-concurrency.d.ts @@ -12,14 +12,14 @@ declare module 'ember-concurrency' { } export interface TaskProperty extends ComputedProperty { - cancelOn(eventNames: string[]): this; + cancelOn(eventNames: string): this; debug(): this; drop(): this; enqueue(): this; group(groupPath: string): this; keepLatest(): this; maxConcurrency(n: number): this; - on(eventNames: string[]): this; + on(eventNames: string): this; restartable(): this; } @@ -50,7 +50,7 @@ declare module 'ember-concurrency' { } type Task = TaskProperty & { - perform: ComputedProperty

; + perform: P; readonly isIdle: boolean; readonly isQueued: boolean; readonly isRunning: boolean; diff --git a/wherehows-web/app/utils/api/datasets/dataset.ts b/wherehows-web/app/utils/api/datasets/dataset.ts index 4e071e19bb..eeb6361da3 100644 --- a/wherehows-web/app/utils/api/datasets/dataset.ts +++ b/wherehows-web/app/utils/api/datasets/dataset.ts @@ -2,11 +2,18 @@ import { warn } from '@ember/debug'; import { IDataset, IDatasetGetResponse, + IDatasetsGetResponse, IDatasetView, - IDatasetViewGetResponse + IDatasetViewGetResponse, + IReadDatasetsOptionBag } from 'wherehows-web/typings/api/datasets/dataset'; import { getHeaders, getJSON } from 'wherehows-web/utils/api/fetcher'; -import { datasetsUrlRoot, datasetUrlById } from 'wherehows-web/utils/api/datasets/shared'; +import { + datasetsCountUrl, + datasetsUrl, + datasetsUrlRoot, + datasetUrlById +} from 'wherehows-web/utils/api/datasets/shared'; import { ApiStatus } from 'wherehows-web/utils/api'; // TODO: DSS-6122 Create and move to Error module @@ -43,8 +50,8 @@ const readDataset = async (id: number | string): Promise => { /** * Reads the response from the datasetView endpoint for the provided dataset id - * @param {number} id - * @returns {Promise} + * @param {number} id + * @returns {Promise} */ const readDatasetView = async (id: number): Promise => { const { status, dataset } = await getJSON({ url: datasetViewUrlById(id) }); @@ -62,7 +69,7 @@ const readDatasetView = async (id: number): Promise => { * @return {string} */ const datasetIdTranslationUrlByUrn = (urn: string): string => { - return `${datasetsUrlRoot}/urntoid/${encodeURIComponent(urn)}`; + return `${datasetsUrlRoot('v1')}/urntoid/${encodeURIComponent(urn)}`; }; /** @@ -92,4 +99,33 @@ const datasetUrnToId = async (urn: string): Promise => { return datasetId; }; -export { readDataset, datasetUrnToId, readDatasetView }; +/** + * Fetches the datasets for a platform, and prefix and returns the list of datasets in the + * response + * @param {IReadDatasetsOptionBag} { + * platform, + * prefix + * } + * @returns {Promise} + */ +const readDatasets = async ({ + platform, + prefix +}: IReadDatasetsOptionBag): Promise => { + const url = datasetsUrl({ platform, prefix }); + const response = await getJSON({ url }); + + return response ? [...response.elements] : []; +}; + +/** + * Gets the number of datasets, if provided, using the platform and prefix also + * @param {Partial} { platform, prefix } + * @returns {Promise} + */ +const readDatasetsCount = async ({ platform, prefix }: Partial): Promise => { + const url = datasetsCountUrl({ platform, prefix }); + return await getJSON({ url }); +}; + +export { readDataset, datasetUrnToId, readDatasetView, readDatasets, readDatasetsCount }; diff --git a/wherehows-web/app/utils/api/datasets/shared.ts b/wherehows-web/app/utils/api/datasets/shared.ts index 25533be152..ba37d24a7a 100644 --- a/wherehows-web/app/utils/api/datasets/shared.ts +++ b/wherehows-web/app/utils/api/datasets/shared.ts @@ -1,14 +1,53 @@ -import { getApiRoot } from 'wherehows-web/utils/api/shared'; +import { IReadDatasetsOptionBag } from 'wherehows-web/typings/api/datasets/dataset'; +import { ApiVersion, getApiRoot } from 'wherehows-web/utils/api/shared'; /** * Defines the endpoint for datasets * @type {string} */ -export const datasetsUrlRoot = `${getApiRoot()}/datasets`; +export const datasetsUrlRoot = (version: ApiVersion) => `${getApiRoot(version)}/datasets`; /** * Constructs a url to get a dataset with a given id * @param {number} id the id of the dataset * @return {string} the dataset url */ -export const datasetUrlById = (id: number): string => `${datasetsUrlRoot}/${id}`; +export const datasetUrlById = (id: number): string => `${datasetsUrlRoot('v1')}/${id}`; + +/** + * Composes the datasets count url from a given platform and or prefix if provided + * @param {Partial} [{ platform, prefix }={}] + * @returns {string} + */ +export const datasetsCountUrl = ({ platform, prefix }: Partial = {}): string => { + const urlRoot = `${datasetsUrlRoot('v2')}/count`; + + if (platform && prefix) { + `${urlRoot}/count`; + } + + if (platform) { + return `${urlRoot}/platform/${platform}`; + } + + return urlRoot; +}; + +/** + * Composes the datasets url using the platform and prefix if one is provided + * @param {IReadDatasetsOptionBag} { platform, prefix } + * @returns {string} + */ +export const datasetsUrl = ({ platform, prefix }: IReadDatasetsOptionBag): string => { + const urlRoot = datasetsUrlRoot('v2'); + + if (platform && prefix) { + return `${urlRoot}/platform/${platform}/${prefix}`; + } + + if (platform) { + return `${urlRoot}/platform/${platform}`; + } + + return urlRoot; +}; diff --git a/wherehows-web/app/utils/api/platforms/platform.ts b/wherehows-web/app/utils/api/platforms/platform.ts new file mode 100644 index 0000000000..3c7194782a --- /dev/null +++ b/wherehows-web/app/utils/api/platforms/platform.ts @@ -0,0 +1,44 @@ +import { IReadDatasetsOptionBag } from 'wherehows-web/typings/api/datasets/dataset'; +import { getJSON } from 'wherehows-web/utils/api/fetcher'; +import { ApiVersion, getApiRoot } from 'wherehows-web/utils/api/shared'; + +/** + * Generates the base url for a platform given a specified ApiVersion + * @param {ApiVersion} version + */ +export const platformsUrlRoot = (version: ApiVersion) => `${getApiRoot(version)}/platforms`; + +/** + * Composes a url for platforms, uses platform and prefix if provided + * @param {IReadDatasetsOptionBag} {platform} + * @param {IReadDatasetsOptionBag} {prefix} + * @returns {string} + */ +const platformsUrl = ({ platform, prefix }: IReadDatasetsOptionBag): string => { + const urlRoot = platformsUrlRoot('v2'); + + if (platform && prefix) { + return `${urlRoot}/${platform}/${prefix}`; + } + + if (platform) { + return `${urlRoot}/${platform}`; + } + + return urlRoot; +}; + +/** + * Reads the platforms endpoint and returns a list of platforms or prefixes found + * @param {IReadDatasetsOptionBag} {platform} + * @param {IReadDatasetsOptionBag} {prefix} + * @returns {Promise>} + */ +const readPlatforms = async ({ platform, prefix }: IReadDatasetsOptionBag): Promise> => { + const url = platformsUrl({ platform, prefix }); + const response = await getJSON>({ url }); + + return response || []; +}; + +export { readPlatforms }; diff --git a/wherehows-web/app/utils/helpers/routes.ts b/wherehows-web/app/utils/helpers/routes.ts new file mode 100644 index 0000000000..83fc78f2a2 --- /dev/null +++ b/wherehows-web/app/utils/helpers/routes.ts @@ -0,0 +1,7 @@ +/** + * For each given query param, creates an object with `refreshModel` set to true + * @param {Array} [params=[]] + * @returns {{}} + */ +export const refreshModelQueryParams = (params: Array = []): {} => + params.reduce((queryParams, param) => ({ ...queryParams, [param]: { refreshModel: true } }), {}); diff --git a/wherehows-web/app/utils/validators/email.js b/wherehows-web/app/utils/validators/email.js deleted file mode 100644 index 9f6fbd1400..0000000000 --- a/wherehows-web/app/utils/validators/email.js +++ /dev/null @@ -1,3 +0,0 @@ -const compliantRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/g; - -export default candidateEmail => compliantRegex.test(String(candidateEmail)); diff --git a/wherehows-web/app/utils/validators/email.ts b/wherehows-web/app/utils/validators/email.ts new file mode 100644 index 0000000000..8225b198fa --- /dev/null +++ b/wherehows-web/app/utils/validators/email.ts @@ -0,0 +1,12 @@ +/** + * Matches an a string to an email pattern + * @type {RegExp} + */ +const emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/g; + +/** + * Checks that a candidate string is an email + * @param candidateEmail {string} + * @return {boolean} + */ +export default (candidateEmail: string): boolean => emailRegex.test(String(candidateEmail)); diff --git a/wherehows-web/app/utils/validators/platform.ts b/wherehows-web/app/utils/validators/platform.ts new file mode 100644 index 0000000000..1f655826c9 --- /dev/null +++ b/wherehows-web/app/utils/validators/platform.ts @@ -0,0 +1,23 @@ +/** + * Matches a string that is prefixed by [platform and suffixed by ] + * Captures the match in the sole capture group + * @type {RegExp} + */ +const platformRegex = /\[platform=([^]]*)]/; + +/** + * Checks that a string represents a dataset platform + * @param {string} candidate + * @returns {boolean} + */ +const isDatasetPlatform = (candidate: string): boolean => platformRegex.test(candidate); + +/** + * Checks that a string represents a dataset prefix + * @param {string} candidate + * @returns {boolean} + */ +const isDatasetPrefix = (candidate: string): boolean => + !isDatasetPlatform(candidate) && ['.', '/'].includes(candidate.slice(-1)); + +export { platformRegex, isDatasetPlatform, isDatasetPrefix }; diff --git a/wherehows-web/mirage/config.ts b/wherehows-web/mirage/config.ts index 0483091791..4e52ed3e1f 100644 --- a/wherehows-web/mirage/config.ts +++ b/wherehows-web/mirage/config.ts @@ -1,4 +1,6 @@ import { faker } from 'ember-cli-mirage'; +import { IFunctionRouteHandler, IMirageServer } from 'wherehows-web/typings/ember-cli-mirage'; +import { ApiStatus } from 'wherehows-web/utils/api/shared'; import { getDatasetColumns } from 'wherehows-web/mirage/helpers/columns'; import { getDatasetCompliance } from 'wherehows-web/mirage/helpers/compliance'; import { getComplianceDataTypes } from 'wherehows-web/mirage/helpers/compliance-data-types'; @@ -17,8 +19,6 @@ import { getDatasetReferences } from 'wherehows-web/mirage/helpers/dataset-refer import { getDatasetSample } from 'wherehows-web/mirage/helpers/dataset-sample'; import { getDatasetView } from 'wherehows-web/mirage/helpers/dataset-view'; import { getOwnerTypes } from 'wherehows-web/mirage/helpers/owner-types'; -import { IFunctionRouteHandler, IMirageServer } from 'wherehows-web/typings/ember-cli-mirage'; -import { ApiStatus } from 'wherehows-web/utils/api/shared'; import { getConfig } from 'wherehows-web/mirage/helpers/config'; import { getAuth } from 'wherehows-web/mirage/helpers/authenticate'; import { aclAuth } from 'wherehows-web/mirage/helpers/aclauth';