mirror of
https://github.com/datahub-project/datahub.git
synced 2025-09-25 17:15:09 +00:00
META-9929, META-9868, resolves related ticket issues and misc tasks
This commit is contained in:
parent
162d52a421
commit
f66f81dc4c
@ -1,10 +1,20 @@
|
||||
import { getJSON, cacheApi } from '@datahub/utils/api/fetcher';
|
||||
import { ApiVersion, getApiRoot } from '@datahub/utils/api/shared';
|
||||
import buildUrl from '@datahub/utils/api/build-url';
|
||||
import { IBrowseParams, IBrowseResponse } from '@datahub/data-models/types/entity/browse';
|
||||
import { IBrowseParams, IBrowseResponse, IBrowsePathParams } from '@datahub/data-models/types/entity/browse';
|
||||
|
||||
/**
|
||||
* URL for browse
|
||||
* @param version api version
|
||||
*/
|
||||
export const browseUrlRoot = (version: ApiVersion): string => `${getApiRoot(version)}/browse`;
|
||||
|
||||
/**
|
||||
* URL for browse paths
|
||||
* @param version api version
|
||||
*/
|
||||
export const browsePathUrlRoot = (version: ApiVersion): string => `${getApiRoot(version)}/browsePaths`;
|
||||
|
||||
/**
|
||||
* Will return the string url for the browse api
|
||||
* @param params GET paramaters required for this API call, see IBrowseParams type
|
||||
@ -14,6 +24,14 @@ const browseUrl = <T>(params: IBrowseParams<T>): string => {
|
||||
return buildUrl(`${urlRoot}`, params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Build GET request for browse
|
||||
* @param params entity type and URN
|
||||
*/
|
||||
const browsePathUrl = (params: IBrowsePathParams): string => {
|
||||
const urlRoot = browsePathUrlRoot(ApiVersion.v2);
|
||||
return buildUrl(`${urlRoot}`, params);
|
||||
};
|
||||
/**
|
||||
* Will fetch browse information for a specific path and entity.
|
||||
* Will return a paginated return of elements and a fixed set of groups/folders.
|
||||
@ -25,3 +43,13 @@ export const readBrowse = cacheApi(
|
||||
return getJSON<IBrowseResponse>({ url });
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Read the path to reach to the specified entity
|
||||
*/
|
||||
export const readBrowsePath = cacheApi(
|
||||
(params: IBrowsePathParams): Promise<Array<string>> => {
|
||||
const url = browsePathUrl(params);
|
||||
return getJSON<Array<string>>({ url });
|
||||
}
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ import { PersonEntity } from '@datahub/data-models/entity/person/person-entity';
|
||||
* Defines the interface for the DataModelEntity enum below.
|
||||
* This allows each entry in the enum to be indexable
|
||||
*/
|
||||
interface IDataModelEntity {
|
||||
export interface IDataModelEntity {
|
||||
[DatasetEntity.displayName]: typeof DatasetEntity;
|
||||
[PersonEntity.displayName]: typeof PersonEntity;
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Constant base for the user profile link. We append the username to this to reach their
|
||||
* profile
|
||||
* @type {string}
|
||||
* @deprecated - should be moved to internal version
|
||||
*/
|
||||
export const profileLinkBase = 'https://cinco.linkedin.biz/people/';
|
@ -13,7 +13,7 @@ import {
|
||||
IEntityLinkAttrsWithCount
|
||||
} from '@datahub/data-models/types/entity/shared';
|
||||
import { NotImplementedError } from '@datahub/data-models/constants/entity/shared/index';
|
||||
import { readBrowse } from '@datahub/data-models/api/browse';
|
||||
import { readBrowse, readBrowsePath } from '@datahub/data-models/api/browse';
|
||||
import { getFacetDefaultValueForEntity } from '@datahub/data-models/entity/utils/facets';
|
||||
import { InstitutionalMemory } from '@datahub/data-models/models/aspects/institutional-memory';
|
||||
|
||||
@ -197,6 +197,31 @@ export abstract class BaseEntity<T extends IBaseEntity> {
|
||||
throw new Error(NotImplementedError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround to get the current static instance
|
||||
* This makes sense if you want to get a static property only
|
||||
* implemented in a subclass, therefore, the same static
|
||||
* class is needed
|
||||
*/
|
||||
get staticInstance(): IBaseEntityStatics<T> {
|
||||
return (this.constructor as unknown) as IBaseEntityStatics<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will read the current path for an entity
|
||||
*/
|
||||
get readPath(): Promise<Array<string>> {
|
||||
const entityName = this.staticInstance.renderProps.search.apiName;
|
||||
return readBrowsePath({
|
||||
type: entityName,
|
||||
urn: this.urn
|
||||
}).then(
|
||||
(paths): Array<string> => {
|
||||
return paths && paths.length > 0 ? paths[0].split('/').filter(Boolean) : [];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously resolves with an instance of T
|
||||
* @readonly
|
||||
@ -219,15 +244,6 @@ export abstract class BaseEntity<T extends IBaseEntity> {
|
||||
throw new Error(NotImplementedError);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @readonly
|
||||
* @type {Array<string>}
|
||||
* @memberof BaseEntity
|
||||
*/
|
||||
get hierarchySegments(): Array<string> {
|
||||
throw new Error(NotImplementedError);
|
||||
}
|
||||
/**
|
||||
* Class properties common across instances
|
||||
* Dictates how visual ui components should be rendered
|
||||
|
@ -10,9 +10,6 @@ import { oneWay } from '@ember/object/computed';
|
||||
import { DatasetPlatform } from '@datahub/metadata-types/constants/entity/dataset/platform';
|
||||
import { decodeUrn } from '@datahub/utils/validators/urn';
|
||||
import { readCategories } from '@datahub/data-models/entity/dataset/read-categories';
|
||||
import { computed } from '@ember/object';
|
||||
import { IDataPlatform } from '@datahub/metadata-types/types/entity/dataset/platform';
|
||||
import { readDataPlatforms } from '@datahub/data-models/api/dataset/platforms';
|
||||
import { getPrefix } from '@datahub/data-models/entity/dataset/utils/segments';
|
||||
import { readDatasetsCount } from '@datahub/data-models/api/dataset/count';
|
||||
import { setProperties } from '@ember/object';
|
||||
@ -71,6 +68,17 @@ export class DatasetEntity extends BaseEntity<IDatasetApiView> {
|
||||
return decodeUrn(this.urn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a link for this specific entity instance, useful for generating a dynamic link from a
|
||||
* single particular dataset entity
|
||||
*/
|
||||
get linkForEntity(): IEntityLinkAttrs {
|
||||
return DatasetEntity.getLinkForEntity({
|
||||
entityUrn: this.urn,
|
||||
displayName: this.name
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the snapshots for a list of Dataset urns
|
||||
* @static
|
||||
@ -109,15 +117,10 @@ export class DatasetEntity extends BaseEntity<IDatasetApiView> {
|
||||
/**
|
||||
* Reference to the data entity's native name, should not be something that is editable but gives us a
|
||||
* more human readable form for the dataset vs the urn
|
||||
* @type {string}
|
||||
*/
|
||||
@oneWay('entity.nativeName')
|
||||
name!: string;
|
||||
|
||||
/**
|
||||
* Reference to the constructed data platform once it is fetched from api
|
||||
*/
|
||||
currentDataPlatform?: IDataPlatform;
|
||||
get name(): string {
|
||||
return this.entity ? this.entity.nativeName : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the Dataset entity identified by this.urn
|
||||
@ -204,26 +207,6 @@ export class DatasetEntity extends BaseEntity<IDatasetApiView> {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces an array of strings depicting the hierarchy of a Feature Entity
|
||||
* @readonly
|
||||
* @type {Array<string>}
|
||||
* @memberof FeatureEntity
|
||||
*/
|
||||
@computed('entity')
|
||||
get hierarchySegments(): Array<string> {
|
||||
const { entity } = this;
|
||||
const { currentDataPlatform } = this;
|
||||
const separator = (currentDataPlatform && currentDataPlatform.datasetNameDelimiter) || '.';
|
||||
|
||||
if (entity) {
|
||||
const { platform, nativeName } = entity;
|
||||
return [platform, ...nativeName.split(separator).filter(Boolean)];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Interim implementation to read categories for datasets
|
||||
* TODO META-8863
|
||||
@ -287,14 +270,10 @@ export class DatasetEntity extends BaseEntity<IDatasetApiView> {
|
||||
export const createDatasetEntity = async (urn: string, fetchedEntity?: IDatasetApiView): Promise<DatasetEntity> => {
|
||||
const dataset = new DatasetEntity(urn);
|
||||
const entity = fetchedEntity || (await dataset.readEntity);
|
||||
// TODO: [META-8652] Find way to separate this from the createDatasetEntity as the concern of creating a dataset
|
||||
// entity should not be related to the concern of reading data platforms
|
||||
const dataPlatforms: Array<IDataPlatform> = await readDataPlatforms();
|
||||
const currentDataPlatform = dataPlatforms.find((platform): boolean => platform.name === entity.platform);
|
||||
|
||||
setProperties(dataset, {
|
||||
entity,
|
||||
currentDataPlatform
|
||||
entity
|
||||
});
|
||||
|
||||
return dataset;
|
||||
};
|
||||
|
@ -6,7 +6,6 @@ import { getTabPropertiesFor } from '@datahub/data-models/entity/utils';
|
||||
/**
|
||||
* Class properties common across instances
|
||||
* Dictates how visual ui components should be rendered
|
||||
* Implemented as a getter to ensure that reads are idempotent
|
||||
* @readonly
|
||||
* @static
|
||||
* @type {IEntityRenderProps}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import getActorFromUrn from '@datahub/data-models/utils/get-actor-from-urn';
|
||||
import { computed } from '@ember/object';
|
||||
import { profileLinkBase } from '@datahub/data-models/constants/entity/person/links';
|
||||
import { NotImplementedError } from '@datahub/data-models/constants/entity/shared';
|
||||
import {
|
||||
getRenderProps,
|
||||
@ -51,7 +50,7 @@ export class PersonEntity extends BaseEntity<IBaseEntity> {
|
||||
* TODO: [META-9698] Migrate to using LiPersonEntity
|
||||
*/
|
||||
static profileLinkFromUsername(username: string): string {
|
||||
return `${profileLinkBase}${username}`;
|
||||
return `${username}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,3 +26,11 @@ export interface IBrowseResponse {
|
||||
groups: Array<{ name: string; count: number }>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Path for bowse paths
|
||||
*/
|
||||
export interface IBrowsePathParams {
|
||||
type: string;
|
||||
urn: string;
|
||||
}
|
||||
|
@ -107,4 +107,4 @@
|
||||
|
||||
</form>
|
||||
|
||||
{{yield}}
|
||||
{{yield}}
|
@ -0,0 +1,47 @@
|
||||
import Component from '@ember/component';
|
||||
// @ts-ignore: Ignore import of compiled template
|
||||
import template from '../templates/components/wait-promise-container';
|
||||
import { containerDataSource } from '@datahub/utils/api/data-source';
|
||||
import { ETaskPromise } from '@datahub/utils/types/concurrency';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { set } from '@ember/object';
|
||||
import { layout } from '@ember-decorators/component';
|
||||
|
||||
/**
|
||||
* WaitPromiseContainer will show spinner while the promise passed is being resolved
|
||||
*
|
||||
* usage:
|
||||
*
|
||||
* const promise = Promise.resolve(3);
|
||||
*
|
||||
* <WaitPromiseContainer @promise={{promise}} as |resolved|>
|
||||
* {{resolved}}
|
||||
* </WaitPromiseContainer>
|
||||
*
|
||||
* should show '3' as the promise resolved '3'
|
||||
*/
|
||||
@layout(template)
|
||||
@containerDataSource('getContainerDataTask', ['promise'])
|
||||
export default class WaitPromiseContainer<T> extends Component {
|
||||
/**
|
||||
* Input promise that we want to wait
|
||||
*/
|
||||
promise?: Promise<T>;
|
||||
|
||||
/**
|
||||
* Value for the resolved promise that will be yielded
|
||||
*/
|
||||
resolved?: T;
|
||||
|
||||
/**
|
||||
* Will fetch and transform api data into status table input format
|
||||
*/
|
||||
@task(function*(this: WaitPromiseContainer<T>): IterableIterator<Promise<T>> {
|
||||
const { promise } = this;
|
||||
if (promise) {
|
||||
const resolved: T = yield promise;
|
||||
set(this, 'resolved', resolved);
|
||||
}
|
||||
})
|
||||
getContainerDataTask!: ETaskPromise<T>;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<ConcurrencyTaskStateHandler @task={{getContainerDataTask}}>
|
||||
{{yield this.resolved}}
|
||||
</ConcurrencyTaskStateHandler>
|
@ -0,0 +1 @@
|
||||
export { default } from '@datahub/shared/components/wait-promise-container';
|
@ -47,6 +47,7 @@
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
"ember-cli-typescript-blueprints": "^2.0.0",
|
||||
"ember-cli-uglify": "^2.1.0",
|
||||
"ember-concurrency": "^1.0.0",
|
||||
"ember-export-application-global": "^2.0.0",
|
||||
"ember-load-initializers": "^2.0.0",
|
||||
"ember-maybe-import-regenerator": "^0.1.6",
|
||||
|
@ -1,2 +1 @@
|
||||
const testemConf = require('../../configs/testem-base');
|
||||
module.exports = testemConf;
|
||||
module.exports = require('../../configs/testem-base');
|
||||
|
@ -0,0 +1,31 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, settled } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | wait-promise-container', function(hooks): void {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function(assert): Promise<void> {
|
||||
let testResolve: (value?: unknown) => void = (): void => {};
|
||||
this.set(
|
||||
'promise',
|
||||
new Promise((resolve): void => {
|
||||
testResolve = resolve;
|
||||
})
|
||||
);
|
||||
await render(hbs`
|
||||
<WaitPromiseContainer @promise={{promise}} as |resolved|>
|
||||
{{resolved}}
|
||||
</WaitPromiseContainer>
|
||||
`);
|
||||
|
||||
assert.equal(this.element.textContent && this.element.textContent.trim(), '');
|
||||
|
||||
testResolve('test');
|
||||
|
||||
// we need to wait to the rerender to happen as setting a property will trigger an ember loop
|
||||
await settled();
|
||||
assert.equal(this.element.textContent && this.element.textContent.trim(), 'test');
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { ITrackingConfig } from '@datahub/shared/types/configurator/tracking';
|
||||
import Service from '@ember/service';
|
||||
import { ApiStatus } from '@datahub/utils/addon/api/shared';
|
||||
import { ApiStatus } from '@datahub/utils/api/shared';
|
||||
|
||||
/**
|
||||
* Describes the interface for the configuration endpoint response object.
|
||||
|
@ -19,6 +19,7 @@ export function populateMockPersonEntity(
|
||||
entity.linkedinProfile = 'https://www.linkedin.com/in/ash-ketchum-b212502a/';
|
||||
entity.slackLink = 'aketchum';
|
||||
entity.skills = ['training', 'catching', 'battling'];
|
||||
|
||||
const managerEntity = new personEntity(personEntity.urnFromUsername('pikachu'));
|
||||
managerEntity.name = 'Pikachu';
|
||||
managerEntity.profilePictureUrl = 'https://pokemonletsgo.pokemon.com/assets/img/common/char-pikachu.png';
|
||||
|
@ -1,12 +1,12 @@
|
||||
import Component from '@ember/component';
|
||||
// @ts-ignore: Ignore import of compiled template
|
||||
import template from '../templates/components/concurrency-task-state-handler';
|
||||
import { layout, classNames } from '@ember-decorators/component';
|
||||
import { layout, tagName } from '@ember-decorators/component';
|
||||
import { action } from '@ember/object';
|
||||
import { Task, task } from 'ember-concurrency';
|
||||
|
||||
@layout(template)
|
||||
@classNames('concurrency-task-state-handler')
|
||||
@tagName('')
|
||||
export default class ConcurrencyTaskStateHandler<T> extends Component {
|
||||
/**
|
||||
* List of arguments to be passed to the task when performed. The array is spread into the task invocation
|
||||
|
@ -30,7 +30,9 @@
|
||||
|
||||
{{else}}
|
||||
{{#if @task.isRunning}}
|
||||
{{pendulum-ellipsis-animation}}
|
||||
<div class="concurrency-task-state-handler">
|
||||
{{pendulum-ellipsis-animation}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{yield}}
|
||||
{{/if}}
|
||||
|
@ -2,6 +2,6 @@ export interface IDynamicLinkNode<T, Z = string, P extends {} = {}> {
|
||||
title: string;
|
||||
text: string;
|
||||
route: Z;
|
||||
model: T;
|
||||
model?: T;
|
||||
queryParams?: P;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import Component from '@ember/component';
|
||||
import { IDynamicLinkNode } from 'wherehows-web/typings/app/browse/dynamic-link';
|
||||
import { IDynamicLinkNode } from '@datahub/utils/types/vendor/dynamic-link';
|
||||
|
||||
/**
|
||||
* Card component. It is used in the home page of the app to
|
||||
|
@ -2,7 +2,7 @@ import Component from '@ember/component';
|
||||
import { tagName } from '@ember-decorators/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { DataModelEntity } from '@datahub/data-models/constants/entity';
|
||||
import { IDynamicLinkNode } from 'wherehows-web/typings/app/browse/dynamic-link';
|
||||
import { IDynamicLinkNode } from '@datahub/utils/types/vendor/dynamic-link';
|
||||
|
||||
/**
|
||||
* Indicates the total number of entities within a category and on user interaction
|
||||
|
@ -296,7 +296,7 @@ export default class DatasetAuthors extends Component {
|
||||
@action
|
||||
confirmSuggestedOwner(this: DatasetAuthors, owner: IOwner): Array<IOwner> | void {
|
||||
const suggestedOwner = { ...owner, source: OwnerSource.Ui };
|
||||
return this.actions.addOwner.call(this, suggestedOwner);
|
||||
return this.addOwner.call(this, suggestedOwner);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -200,14 +200,30 @@ export default class DatasetMainContainer extends Component {
|
||||
// TODO should fetch the dataset itself, but right now dataset is fetched at route level
|
||||
const { dataset } = this;
|
||||
|
||||
//TODO: META-8267 Container notifications decorator
|
||||
if (dataset) {
|
||||
const entity: DatasetEntity = yield createDatasetEntity(this.dataset.uri, this.dataset as IDatasetApiView);
|
||||
|
||||
set(this, 'entity', entity);
|
||||
}
|
||||
}).restartable())
|
||||
reifyEntityTask!: ETaskPromise<DatasetEntity>;
|
||||
|
||||
/**
|
||||
* This will return the paths for an entity. We should be able to consume entity.readPath
|
||||
* direcly but since datasets are not migrated we need to flag guard it.
|
||||
*
|
||||
* TODO META-8863 Interim implementation to read categories for datasets
|
||||
*/
|
||||
@computed('entity')
|
||||
get paths(): Promise<Array<string>> {
|
||||
const { entity } = this;
|
||||
if (!entity) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
return entity.readPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the uri on a model to a usable URN format
|
||||
* @type {ComputedProperty<string>}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { tagName } from '@ember-decorators/component';
|
||||
import { IDynamicLinkNode } from 'wherehows-web/typings/app/browse/dynamic-link';
|
||||
import { IDynamicLinkNode } from '@datahub/utils/types/vendor/dynamic-link';
|
||||
|
||||
/**
|
||||
* Query params with pages for routes
|
||||
|
@ -4,6 +4,7 @@ import SearchService from 'wherehows-web/services/search';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { grammarProcessingSteps, typeaheadQueryProcessor } from 'wherehows-web/utils/parsers/autocomplete';
|
||||
import { ISuggestionGroup } from 'wherehows-web/utils/parsers/autocomplete/types';
|
||||
import { DatasetEntity } from '@datahub/data-models/entity/dataset/dataset-entity';
|
||||
import { tagName } from '@ember-decorators/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { DataModelEntity, DataModelName } from '@datahub/data-models/constants/entity';
|
||||
@ -68,7 +69,7 @@ export default class SearchBoxContainer extends Component {
|
||||
* @param {Entity} [entity]
|
||||
* @memberof SearchBoxContainer
|
||||
*/
|
||||
onSearch(text: string = '', entity: DataModelEntity['displayName'] = 'datasets'): void {
|
||||
onSearch(text: string = '', entity: DataModelName = DatasetEntity.displayName): void {
|
||||
// entity (dropdown value) might be different than this.entity (Page that you are in)
|
||||
const dataModelEntity = DataModelEntity[entity];
|
||||
const { attributes } = dataModelEntity.renderProps.search;
|
||||
|
@ -17,13 +17,18 @@ const fallback = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAA
|
||||
const makeAvatar = ({ aviUrlPrimary, aviUrlFallback = fallback }: IAppConfig['userEntityProps']): AvatarCreatorFunc => (
|
||||
object: Partial<IAvatar>
|
||||
): IAvatar => {
|
||||
const props = pick(object, ['email', 'userName', 'name', 'imageUrl']);
|
||||
const { userName } = props;
|
||||
const imageFallback = aviUrlFallback || fallback;
|
||||
const props = pick(object, ['email', 'userName', 'name', 'imageUrl', 'pictureLink']);
|
||||
const { userName, pictureLink } = props;
|
||||
const imageUrlFallback = aviUrlFallback || fallback;
|
||||
const imageUrl = pictureLink
|
||||
? pictureLink
|
||||
: userName && aviUrlPrimary
|
||||
? aviUrlPrimary.replace('[username]', userName)
|
||||
: imageUrlFallback;
|
||||
|
||||
return {
|
||||
imageUrl: userName && aviUrlPrimary ? aviUrlPrimary.replace('[username]', userName) : imageFallback,
|
||||
imageUrlFallback: imageFallback,
|
||||
imageUrlFallback,
|
||||
imageUrl,
|
||||
...props
|
||||
};
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ import Search from 'wherehows-web/services/search';
|
||||
import { set } from '@ember/object';
|
||||
import { DataModelEntity } from '@datahub/data-models/constants/entity';
|
||||
import { unGuardedEntities } from 'wherehows-web/utils/entity/flag-guard';
|
||||
import { IDynamicLinkNode } from 'wherehows-web/typings/app/browse/dynamic-link';
|
||||
import { IDynamicLinkNode } from '@datahub/utils/types/vendor/dynamic-link';
|
||||
import { capitalize } from '@ember/string';
|
||||
import { getConfig } from 'wherehows-web/services/configurator';
|
||||
|
||||
|
@ -115,6 +115,7 @@ $nacho-breadcrumbs-arrow-padding: 20px;
|
||||
top: $application-navbar-static-height;
|
||||
z-index: z('nav') + z('below'); // To give precendence to tooltip and dropdown
|
||||
width: 100%;
|
||||
min-height: $nacho-breadcrumbs-height;
|
||||
|
||||
&--with-banner-offset {
|
||||
top: $banner-alerts-height + $application-navbar-static-height;
|
||||
|
@ -2,11 +2,11 @@
|
||||
<section class="browse-category-container {{if (eq model.segments.length 0) "browse-category-container--no-breadcrumbs"}}">
|
||||
{{!-- Dont show breadcrumbs first level since we already have the title and there is real action --}}
|
||||
{{#if (gt model.segments.length 0)}}
|
||||
{{browser/entity-breadcrumbs
|
||||
class=(with-banner-offset "nacho-breadcrumbs-container")
|
||||
entity=(lowercase model.displayName)
|
||||
segments=model.segments
|
||||
}}
|
||||
<Browser::EntityBreadcrumbs
|
||||
class={{with-banner-offset "nacho-breadcrumbs-container"}}
|
||||
@entity={{lowercase model.displayName}}
|
||||
@segments={{model.segments}}
|
||||
/>
|
||||
{{/if}}
|
||||
<div class="container">
|
||||
|
||||
|
@ -69,17 +69,6 @@
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="dataset-author">
|
||||
{{datasets/owners/suggested-owners
|
||||
owners=systemGeneratedOwnersWithAvatars
|
||||
ownerTypes=ownerTypes
|
||||
commonOwners=commonOwners
|
||||
removeOwner=(action "removeOwner")
|
||||
confirmSuggestedOwner=(action "confirmSuggestedOwner")
|
||||
updateOwnerType=(action "updateOwnerType")
|
||||
}}
|
||||
</section>
|
||||
|
||||
<section class="action-bar">
|
||||
<div class="container action-bar__content">
|
||||
<button
|
||||
@ -113,4 +102,4 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
@ -21,6 +21,7 @@
|
||||
notifications=notificationsMap
|
||||
headerComponent=headerComponent
|
||||
fields=fields
|
||||
paths=this.paths
|
||||
tabSelectionChanged=(action tabSelectionChanged)
|
||||
setOwnershipRuleChange=(action setOwnershipRuleChange)
|
||||
)
|
||||
|
@ -48,22 +48,6 @@
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="dataset-header-meta">
|
||||
{{#datasets/containers/dataset-fabrics
|
||||
class="dataset-fabric-row-container"
|
||||
urn=model.uri
|
||||
as |fabricsContainer|
|
||||
}}
|
||||
<div class="dataset-fabric-entity-container">
|
||||
<strong>Fabric:</strong>
|
||||
{{datasets/dataset-fabric-switcher
|
||||
urn=fabricsContainer.urn
|
||||
fabrics=fabricsContainer.fabrics
|
||||
}}
|
||||
</div>
|
||||
{{/datasets/containers/dataset-fabrics}}
|
||||
</div>
|
||||
|
||||
{{datasets/containers/dataset-owner-list
|
||||
urn=encodedUrn
|
||||
avatarEntityProps=avatarEntityProps
|
||||
|
@ -8,11 +8,16 @@ as |container|
|
||||
as |snapshot|
|
||||
}}
|
||||
<div id="dataset" class="entity-header">
|
||||
{{browser/entity-breadcrumbs
|
||||
class=(with-banner-offset "nacho-breadcrumbs-container")
|
||||
entity=(or (lowercase container.entity.displayName) "--")
|
||||
segments=container.entity.hierarchySegments
|
||||
}}
|
||||
<WaitPromiseContainer
|
||||
class={{with-banner-offset "nacho-breadcrumbs-container"}}
|
||||
@promise={{container.paths}} as |paths|>
|
||||
<Browser::EntityBreadcrumbs
|
||||
@tagName=""
|
||||
@entity={{or (lowercase container.entity.displayName) "--"}}
|
||||
@segments={{paths}}
|
||||
/>
|
||||
</WaitPromiseContainer>
|
||||
|
||||
<div class="container">
|
||||
{{component
|
||||
container.headerComponent
|
||||
|
@ -9,6 +9,9 @@ export interface IAvatar {
|
||||
imageUrl: string;
|
||||
// Fallback url for avatar image on error
|
||||
imageUrlFallback: string;
|
||||
// Alternate url for avatar image
|
||||
pictureLink?: string;
|
||||
// Email address for the associated entity if available
|
||||
email?: null | string;
|
||||
// Handle for the avatar
|
||||
userName?: string;
|
||||
|
@ -23,6 +23,7 @@ import { browse } from 'wherehows-web/mirage/helpers/browse';
|
||||
import searchResponse from 'wherehows-web/mirage/fixtures/search-response';
|
||||
import { getSamplePageViewResponse } from 'wherehows-web/mirage/helpers/search/pageview-response';
|
||||
import { getEntitySearchResults } from 'wherehows-web/mirage/helpers/search/entity';
|
||||
import { browsePaths } from 'wherehows-web/mirage/helpers/browse-paths';
|
||||
|
||||
export default function(this: IMirageServer): void {
|
||||
this.get('/config', getConfig);
|
||||
@ -37,6 +38,8 @@ export default function(this: IMirageServer): void {
|
||||
|
||||
this.get('/browse', browse);
|
||||
|
||||
this.get('/browsePaths', browsePaths);
|
||||
|
||||
this.get('/datasets/:identifier/', getDatasetView);
|
||||
|
||||
this.get('/datasets/:identifier/owners', getDatasetOwners);
|
||||
|
@ -47,8 +47,9 @@ export default Factory.extend({
|
||||
userEntityProps() {
|
||||
return {
|
||||
aviUrlPrimary:
|
||||
'https://cinco.corp.linkedin.com/api/profile/[username]/picture?access_token=2rzmbzEMGlHsszQktFY-B1TxUic',
|
||||
aviUrlFallback: 'https://static.licdn-ei.com/sc/h/djzv59yelk5urv2ujlazfyvrk'
|
||||
'https://raw.githubusercontent.com/linkedin/WhereHows/datahub/datahub-web/packages/data-portal/public/assets/images/default_avatar.png',
|
||||
aviUrlFallback:
|
||||
'https://raw.githubusercontent.com/linkedin/WhereHows/datahub/datahub-web/packages/data-portal/public/assets/images/default_avatar.png'
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { getDatasetUrnParts } from '@datahub/data-models/entity/dataset/utils/urn';
|
||||
import { FabricType } from '@datahub/metadata-types/constants/common/fabric-type';
|
||||
|
||||
export const browsePaths = (_schema: any, request: any): Array<string> => {
|
||||
const {
|
||||
queryParams: { urn }
|
||||
} = request;
|
||||
|
||||
const { platform, prefix = '', fabric = FabricType.PROD } = getDatasetUrnParts(urn);
|
||||
return [`${fabric}/${platform}/${prefix}`];
|
||||
};
|
@ -11,14 +11,15 @@ module('Acceptance | breadcrumbs-smoke-test', function(hooks) {
|
||||
|
||||
test('Breadcrumbs Smoke Test', async function(this: IMirageTestContext, assert) {
|
||||
const categoryLinkClass = '.browse-category__link';
|
||||
const breadcrumbsClass = '.nacho-breadcrumbs__crumb';
|
||||
const breadCrumbClass = '.nacho-breadcrumbs';
|
||||
const breadCrumbsClass = `${breadCrumbClass}__crumb`;
|
||||
defaultScenario(this.server);
|
||||
await appLogin();
|
||||
await visit('/');
|
||||
await visit('/browse/datasets?path=hdfs');
|
||||
await waitFor(categoryLinkClass);
|
||||
let categoryLinks = findAll(categoryLinkClass);
|
||||
let breadcrumbs = findAll(breadcrumbsClass);
|
||||
let breadcrumbs = findAll(breadCrumbsClass);
|
||||
|
||||
assert.equal(currentURL(), '/browse/datasets?path=hdfs');
|
||||
assert.equal(categoryLinks.length, 1, 'There is 1 folder in root hdfs');
|
||||
@ -30,7 +31,7 @@ module('Acceptance | breadcrumbs-smoke-test', function(hooks) {
|
||||
await click(`${categoryLinkClass}:first-child`);
|
||||
await waitFor(categoryLinkClass);
|
||||
categoryLinks = findAll(categoryLinkClass);
|
||||
breadcrumbs = findAll(breadcrumbsClass);
|
||||
breadcrumbs = findAll(breadCrumbsClass);
|
||||
|
||||
assert.equal(currentURL(), '/browse/datasets?path=hdfs%2Fsome');
|
||||
assert.equal(categoryLinks.length, 1, 'There is 1 folder in path');
|
||||
@ -42,7 +43,7 @@ module('Acceptance | breadcrumbs-smoke-test', function(hooks) {
|
||||
await click(`${categoryLinkClass}:first-child`);
|
||||
await waitFor(categoryLinkClass);
|
||||
categoryLinks = findAll(categoryLinkClass);
|
||||
breadcrumbs = findAll(breadcrumbsClass);
|
||||
breadcrumbs = findAll(breadCrumbsClass);
|
||||
|
||||
assert.equal(currentURL(), '/browse/datasets?path=hdfs%2Fsome%2Fpath');
|
||||
assert.equal(categoryLinks.length, 1, 'There is 1 folder in path');
|
||||
@ -54,7 +55,7 @@ module('Acceptance | breadcrumbs-smoke-test', function(hooks) {
|
||||
await click(`${categoryLinkClass}:first-child`);
|
||||
await waitFor(categoryLinkClass, { count: 2 });
|
||||
categoryLinks = findAll(categoryLinkClass);
|
||||
breadcrumbs = findAll(breadcrumbsClass);
|
||||
breadcrumbs = findAll(breadCrumbsClass);
|
||||
|
||||
assert.equal(currentURL(), '/browse/datasets?path=hdfs%2Fsome%2Fpath%2Fwith');
|
||||
assert.equal(categoryLinks.length, 2, 'There are 2 folder in path');
|
||||
@ -66,7 +67,7 @@ module('Acceptance | breadcrumbs-smoke-test', function(hooks) {
|
||||
await click(`${categoryLinkClass}:first-child`);
|
||||
await waitFor(categoryLinkClass, { count: 2 });
|
||||
categoryLinks = findAll(categoryLinkClass);
|
||||
breadcrumbs = findAll(breadcrumbsClass);
|
||||
breadcrumbs = findAll(breadCrumbsClass);
|
||||
|
||||
assert.equal(currentURL(), '/browse/datasets?path=hdfs%2Fsome%2Fpath%2Fwith%2Fdirectories');
|
||||
assert.equal(categoryLinks.length, 2, 'There is only 2 datasets in path');
|
||||
@ -76,29 +77,13 @@ module('Acceptance | breadcrumbs-smoke-test', function(hooks) {
|
||||
|
||||
// path adataset1
|
||||
await click(`${categoryLinkClass}:first-child`);
|
||||
breadcrumbs = findAll(breadcrumbsClass);
|
||||
breadcrumbs = findAll(breadCrumbsClass);
|
||||
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
'/datasets/urn:li:dataset:(urn:li:dataPlatform:hdfs,%2Fsome%2Fpath%2Fwith%2Fdirectories%2Fadataset1,PROD)/schema'
|
||||
);
|
||||
assert.equal(breadcrumbs.length, 7, 'There are 7 links in breadcrumb');
|
||||
assert.equal(
|
||||
getTextNoSpacesFromElements(breadcrumbs),
|
||||
'Datasetshdfssomepathwithdirectoriesadataset1',
|
||||
'Text match'
|
||||
);
|
||||
|
||||
// path /some/path/with/directories
|
||||
await click(`${breadcrumbsClass}:nth-child(6) a`);
|
||||
await waitFor(categoryLinkClass, { count: 2 });
|
||||
categoryLinks = findAll(categoryLinkClass);
|
||||
breadcrumbs = findAll(breadcrumbsClass);
|
||||
|
||||
assert.equal(currentURL(), '/browse/datasets?page=1&path=hdfs%2Fsome%2Fpath%2Fwith%2Fdirectories');
|
||||
assert.equal(categoryLinks.length, 2, 'There is only 2 datasets in path');
|
||||
assert.equal(getTextNoSpacesFromElements(categoryLinks), 'adataset1adataset2', 'Text match');
|
||||
assert.equal(breadcrumbs.length, 6, 'There are 6 links in breadcrumb');
|
||||
assert.equal(getTextNoSpacesFromElements(breadcrumbs), 'Datasetshdfssomepathwithdirectories', 'Text match');
|
||||
assert.equal(breadcrumbs.length, 8, 'There are 8 links in breadcrumb');
|
||||
assert.dom(breadCrumbClass).hasText('Datasets PROD hdfs some path with directories adataset1');
|
||||
});
|
||||
});
|
||||
|
@ -6,17 +6,17 @@ import hbs from 'htmlbars-inline-precompile';
|
||||
const componentClassName = '.nacho-breadcrumbs-container';
|
||||
const breadcrumbsListClassName = '.nacho-breadcrumbs';
|
||||
|
||||
module('Integration | Component | browser/entity-breadcrumbs', function(hooks) {
|
||||
module('Integration | Component | browser/entity-breadcrumbs', function(hooks): void {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('Breadcrumbs component rendering', async function(assert) {
|
||||
test('Breadcrumbs component rendering', async function(assert): Promise<void> {
|
||||
const entity = 'Test Entity';
|
||||
let segments: Array<string> = [];
|
||||
|
||||
this.setProperties({ segments, entity });
|
||||
|
||||
await render(hbs`
|
||||
{{browser/entity-breadcrumbs segments=segments entity=entity}}
|
||||
<Browser::EntityBreadcrumbs @segments={{segments}} @entity={{entity}} />
|
||||
`);
|
||||
|
||||
assert.dom(componentClassName).isVisible();
|
||||
|
@ -4,7 +4,7 @@ import { render, findAll, click } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
import { noop } from 'wherehows-web/utils/helpers/functions';
|
||||
import { OwnerType, OwnerSource } from 'wherehows-web/utils/api/datasets/owners';
|
||||
import { OwnerType } from 'wherehows-web/utils/api/datasets/owners';
|
||||
import owners from 'wherehows-web/mirage/fixtures/owners';
|
||||
import userStub from 'wherehows-web/tests/stubs/services/current-user';
|
||||
import { minRequiredConfirmedOwners } from 'wherehows-web/constants/datasets/owner';
|
||||
@ -29,7 +29,7 @@ module('Integration | Component | dataset authors', function(hooks) {
|
||||
this.set('saveOwnerChanges', noop);
|
||||
await render(hbs`{{dataset-authors owners=owners ownerTypes=ownerTypes save=(action saveOwnerChanges)}}`);
|
||||
|
||||
assert.equal(findAll('.dataset-author').length, 2, 'expected two dataset author components to be rendered');
|
||||
assert.equal(findAll('.dataset-author').length, 1, 'expected two dataset author components to be rendered');
|
||||
});
|
||||
|
||||
test('it should remove an owner when removeOwner is invoked', async function(assert) {
|
||||
@ -43,39 +43,6 @@ module('Integration | Component | dataset authors', function(hooks) {
|
||||
assert.equal(this.get('owners').length, 0);
|
||||
});
|
||||
|
||||
test('it should update a suggested owner to confirmed', async function(assert) {
|
||||
assert.expect(3);
|
||||
const [owner, suggestedOwner] = owners;
|
||||
const resolvedOwners = [owner];
|
||||
const suggestedOwners = [suggestedOwner];
|
||||
|
||||
const initialLength = resolvedOwners.length;
|
||||
let userName, confirmedOwner;
|
||||
|
||||
this.set('owners', resolvedOwners);
|
||||
this.set('ownerTypes', ownerTypes);
|
||||
this.set('saveOwnerChanges', noop);
|
||||
this.set('suggestedOwners', suggestedOwners);
|
||||
await render(
|
||||
hbs`{{dataset-authors owners=owners suggestedOwners=suggestedOwners ownerTypes=ownerTypes save=(action saveOwnerChanges)}}`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
this.get('owners.length'),
|
||||
initialLength,
|
||||
`the list of owners is ${initialLength} before adding confirmed owner`
|
||||
);
|
||||
|
||||
await click('.dataset-authors-suggested__info__trigger');
|
||||
await click('.suggested-owner-card__owner-info__add button');
|
||||
|
||||
userName = this.get('current-user.currentUser.userName');
|
||||
confirmedOwner = this.get('owners').findBy('confirmedBy', userName);
|
||||
|
||||
assert.equal(this.get('owners.length'), initialLength + 1, 'the list of owner contains one more new owner');
|
||||
assert.equal(confirmedOwner.source, OwnerSource.Ui, 'contains a new owner with ui source');
|
||||
});
|
||||
|
||||
test('it should disable the save button when confirmedOwners is less than required minimum', async function(assert) {
|
||||
assert.expect(2);
|
||||
this.set('owners', [confirmedOwner]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user