mirror of
https://github.com/datahub-project/datahub.git
synced 2025-11-01 11:19:05 +00:00
Misc. bug fixes related to search and person entity display attributes, and lost picture url for person entity
This commit is contained in:
parent
d8c594aeb9
commit
06d19d2958
@ -11,7 +11,7 @@ import { BaseEntity, statics, IBaseEntityStatics } from '@datahub/data-models/en
|
||||
import { IEntityRenderProps } from '@datahub/data-models/types/entity/rendering/entity-render-props';
|
||||
import { DataModelEntity } from '@datahub/data-models/constants/entity';
|
||||
import { IPersonEntityEditableProperties } from '@datahub/data-models/types/entity/person/props';
|
||||
import { ICorpUserInfo } from '@datahub/metadata-types/types/entity/person/person-entity';
|
||||
import { ICorpUserEditableInfo, ICorpUserInfo } from '@datahub/metadata-types/types/entity/person/person-entity';
|
||||
import { readPerson, saveEditablePersonalInfo } from '@datahub/data-models/api/person/entity';
|
||||
import { alias, not } from '@ember/object/computed';
|
||||
|
||||
@ -164,6 +164,11 @@ export class PersonEntity extends BaseEntity<ICorpUserInfo> {
|
||||
@alias('entity.info.email')
|
||||
email!: string;
|
||||
|
||||
/**
|
||||
* References the pictureLink on the editableInfo for the Person Entity
|
||||
*/
|
||||
@alias('entity.editableInfo.pictureLink')
|
||||
pictureLink!: ICorpUserEditableInfo['pictureLink'];
|
||||
/**
|
||||
* A list of skills that this particular person entity has declared to own.
|
||||
*/
|
||||
@ -287,7 +292,8 @@ export class PersonEntity extends BaseEntity<ICorpUserInfo> {
|
||||
return saveEditablePersonalInfo(this.urn, {
|
||||
teams: props.teamTags,
|
||||
aboutMe: props.focusArea,
|
||||
skills: props.skills
|
||||
skills: props.skills,
|
||||
pictureLink: this.pictureLink
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ export const getRenderProps = (): IEntityRenderProps => {
|
||||
searchResultEntityFields: {
|
||||
description: 'title',
|
||||
pictureUrl: 'editableInfo.pictureLink',
|
||||
name: 'username'
|
||||
name: 'info.fullName'
|
||||
},
|
||||
showFacets: false,
|
||||
placeholder: 'Search for People...',
|
||||
|
||||
@ -70,13 +70,6 @@ export default class EntityDeprecation extends Component {
|
||||
*/
|
||||
centeredDate: Date = this.selectedDate;
|
||||
|
||||
/**
|
||||
* Before a user can update the deprecation status to deprecated, they must acknowledge that even if the
|
||||
* entity is deprecated they must still keep it compliant.
|
||||
* @type {boolean}
|
||||
*/
|
||||
isDeprecationAcknowledged: boolean = false;
|
||||
|
||||
/**
|
||||
* Expected to be passed in if we plan on using the default entity deprecation acknowledgement template,
|
||||
* leads to a more info link for the user about deprecation of such entity
|
||||
@ -84,14 +77,6 @@ export default class EntityDeprecation extends Component {
|
||||
*/
|
||||
entityDecommissionWikiLink?: string;
|
||||
|
||||
/**
|
||||
* Optionally passed in if the entity should have an acknowledgement message/structure that differs from
|
||||
* our default provided partial. If not passed in, constructor will automatically populate this with the
|
||||
* default acknowledgement
|
||||
* @type {string}
|
||||
*/
|
||||
deprecationAcknowledgementTemplate!: string;
|
||||
|
||||
/**
|
||||
* The earliest date a user can select as a decommission date
|
||||
* @type {Date}
|
||||
@ -116,13 +101,6 @@ export default class EntityDeprecation extends Component {
|
||||
@reads('deprecationNote')
|
||||
deprecationNoteAlias!: EntityDeprecation['deprecationNote'];
|
||||
|
||||
didInsertElement(): void {
|
||||
super.didInsertElement();
|
||||
|
||||
typeof this.deprecationAcknowledgementTemplate === 'string' ||
|
||||
set(this, 'deprecationAcknowledgementTemplate', 'partials/entity-deprecation/default-acknowledgement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the save action with the updated values for deprecated status, decommission time, and
|
||||
* deprecation note. The actual save functionality should be handled by
|
||||
@ -155,13 +133,4 @@ export default class EntityDeprecation extends Component {
|
||||
onDecommissionDateChange(decommissionTime: Date): void {
|
||||
set(this, 'decommissionTime', new Date(decommissionTime).getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* When a user clicks the checkbox to acknolwedge or cancel the acknolwedgement of the notice for
|
||||
* deprecating an entity
|
||||
*/
|
||||
@action
|
||||
onAcknowledgeDeprecationNotice(): void {
|
||||
this.toggleProperty('isDeprecationAcknowledged');
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,27 +80,13 @@
|
||||
{{if decommissionTime (concat 'Will be decommissioned on ' (moment-format decommissionTime 'MMMM DD YYYY')) ''}}
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<span>
|
||||
{{input
|
||||
type="checkbox"
|
||||
name="deprecated.acknowledge"
|
||||
id="acknowledge-deprecation"
|
||||
checked=(readonly isDeprecationAcknowledged)
|
||||
change=(action "onAcknowledgeDeprecationNotice")}}
|
||||
|
||||
<label for="acknowledge-deprecation">
|
||||
{{partial deprecationAcknowledgementTemplate}}
|
||||
</label>
|
||||
</span>
|
||||
|
||||
{{/if}}
|
||||
|
||||
<div class="entity-deprecation__actions">
|
||||
<button
|
||||
type="submit"
|
||||
class="nacho-button--large nacho-button--secondary"
|
||||
disabled={{and isDeprecatedAlias (or (not decommissionTime) (not isDeprecationAcknowledged))}}>
|
||||
disabled={{and isDeprecatedAlias (not decommissionTime)}}>
|
||||
Update Deprecation Status
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, find, findAll, click, fillIn } from '@ember/test-helpers';
|
||||
import { render, find, findAll } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
const findInput = (selector: string): HTMLInputElement => {
|
||||
@ -67,7 +67,6 @@ module('Integration | Component | entity-deprecation', function(hooks): void {
|
||||
|
||||
test('decommissionTime', async function(assert): Promise<void> {
|
||||
let isDisabled;
|
||||
assert.expect(3);
|
||||
|
||||
this.setProperties({
|
||||
...defaultProperties,
|
||||
@ -83,58 +82,5 @@ module('Integration | Component | entity-deprecation', function(hooks): void {
|
||||
|
||||
isDisabled = findInput('.entity-deprecation__actions [type=submit]').disabled;
|
||||
assert.ok(isDisabled, 'submit button is disabled');
|
||||
|
||||
this.setProperties({ decommissionTime: new Date(), isDirty: true });
|
||||
await render(hbs`{{entity-deprecation
|
||||
entityName=entityName
|
||||
isDeprecated=deprecated
|
||||
decommissionTime=decommissionTime
|
||||
updateDeprecation=(action updateDeprecation)}}`);
|
||||
|
||||
await fillIn('.entity-deprecation__note-editor .medium-editor-element', 'text');
|
||||
|
||||
isDisabled = findInput('.entity-deprecation__actions [type=submit]').disabled;
|
||||
assert.ok(isDisabled, 'submit button is disabled if we only fill in decomissionTime');
|
||||
|
||||
await click('#acknowledge-deprecation');
|
||||
|
||||
isDisabled = findInput('.entity-deprecation__actions [type=submit]').disabled;
|
||||
assert.notOk(isDisabled, 'submit button is disabled if we only fill in decomissionTime');
|
||||
});
|
||||
|
||||
test('triggers the onUpdateDeprecation action when submitted', async function(assert): Promise<void> {
|
||||
let submitActionCallCount = 0;
|
||||
|
||||
this.setProperties({
|
||||
...defaultProperties,
|
||||
submit(deprecated: boolean, note: string): void {
|
||||
submitActionCallCount++;
|
||||
assert.equal(deprecated, true, 'action is called with deprecation value of true');
|
||||
assert.equal(note, '', 'action is called with an empty deprecation note');
|
||||
},
|
||||
decommissionTime: new Date()
|
||||
});
|
||||
|
||||
await render(hbs`{{entity-deprecation
|
||||
entityName=entityName
|
||||
isDeprecated=deprecated
|
||||
decommissionTime=decommissionTime
|
||||
updateDeprecation=(action submit)}}`);
|
||||
|
||||
assert.equal(submitActionCallCount, 0, 'action is not called on render');
|
||||
assert.equal(
|
||||
(find('#dataset-is-deprecated') as HTMLInputElement).checked,
|
||||
false,
|
||||
'deprecation checkbox is unchecked'
|
||||
);
|
||||
|
||||
await click('#dataset-is-deprecated');
|
||||
|
||||
assert.equal((find('#dataset-is-deprecated') as HTMLInputElement).checked, true, 'deprecation checkbox is checked');
|
||||
|
||||
await click('#acknowledge-deprecation');
|
||||
await click('.entity-deprecation__actions [type=submit]');
|
||||
|
||||
assert.equal(submitActionCallCount, 1, 'action is called once');
|
||||
});
|
||||
});
|
||||
|
||||
@ -8,6 +8,8 @@ export interface ICorpUserEditableInfo {
|
||||
teams: Array<string>;
|
||||
// A self-assigned list of skills that the person claims to own
|
||||
skills: Array<string>;
|
||||
// Reference to the picture URL for the person
|
||||
pictureLink: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -26,6 +26,7 @@ import { NotificationEvent } from '@datahub/utils/constants/notifications';
|
||||
import { PersonEntity } from '@datahub/data-models/entity/person/person-entity';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { ETaskPromise } from '@datahub/utils/types/concurrency';
|
||||
import DataModelsService from '@datahub/data-models/services/data-models';
|
||||
|
||||
type Comparator = -1 | 0 | 1;
|
||||
|
||||
@ -89,6 +90,12 @@ export default class DatasetAuthors extends Component {
|
||||
@service('user-lookup')
|
||||
userLookup: UserLookup;
|
||||
|
||||
/**
|
||||
* Injected service for our data models getter to access the PersonEntity class
|
||||
*/
|
||||
@service('data-models')
|
||||
dataModels!: DataModelsService;
|
||||
|
||||
/**
|
||||
* If there are no changes to the ownership tab, we want to keep the save button disabled. Rather than
|
||||
* try to compare two sets of prev vs new data, we just have a flag here that short stops the validation
|
||||
@ -187,14 +194,15 @@ export default class DatasetAuthors extends Component {
|
||||
* @returns {OwnerWithAvatarRecord}
|
||||
*/
|
||||
datasetAuthorsOwnersAugmentedWithAvatars = (owner: IOwner): OwnerWithAvatarRecord => {
|
||||
const { avatarProperties } = this;
|
||||
const { avatarProperties, dataModels } = this;
|
||||
const PersonEntityClass = dataModels.getModel('people') as typeof PersonEntity;
|
||||
|
||||
return {
|
||||
owner,
|
||||
avatar: avatarProperties
|
||||
? makeAvatar(avatarProperties)(owner)
|
||||
: { imageUrl: '', imageUrlFallback: '/assets/images/default_avatar.png' },
|
||||
profile: PersonEntity.profileLinkFromUsername(owner.userName)
|
||||
profile: PersonEntityClass.urnFromUsername(owner.userName)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ import { facetToParamUrl } from 'wherehows-web/utils/api/search/search';
|
||||
import RouterService from '@ember/routing/router-service';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { ETaskPromise } from '@datahub/utils/types/concurrency';
|
||||
import DataModelsService from '@datahub/data-models/services/data-models';
|
||||
|
||||
/**
|
||||
* Search box container that handle all the data part for
|
||||
@ -29,6 +30,9 @@ export default class SearchBoxContainer extends Component {
|
||||
@service
|
||||
search: SearchService;
|
||||
|
||||
@service('data-models')
|
||||
dataModels!: DataModelsService;
|
||||
|
||||
@alias('search.entity')
|
||||
entity?: DataModelName;
|
||||
|
||||
@ -44,9 +48,9 @@ export default class SearchBoxContainer extends Component {
|
||||
*/
|
||||
@computed('entity')
|
||||
get dataModelEntity(): DataModelEntity | undefined {
|
||||
const { entity } = this;
|
||||
if (entity) {
|
||||
return DataModelEntity[entity];
|
||||
const { entity, dataModels } = this;
|
||||
if (entity && dataModels) {
|
||||
return dataModels.getModel(entity);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -58,8 +62,15 @@ export default class SearchBoxContainer extends Component {
|
||||
* @returns {(IterableIterator<Promise<Array<ISuggestionGroup>>>)}
|
||||
* @memberof SearchBoxContainer
|
||||
*/
|
||||
@task(function*(query: string = '', entity?: DataModelName): IterableIterator<Promise<Array<ISuggestionGroup>>> {
|
||||
return yield typeaheadQueryProcessor(query, entity, grammarProcessingSteps);
|
||||
@task(function*(
|
||||
this: SearchBoxContainer,
|
||||
query: string = '',
|
||||
entity?: DataModelName
|
||||
): IterableIterator<Promise<Array<ISuggestionGroup>>> {
|
||||
const { dataModels } = this;
|
||||
if (entity) {
|
||||
return yield typeaheadQueryProcessor(query, dataModels.getModel(entity), grammarProcessingSteps);
|
||||
}
|
||||
})
|
||||
onSearchInputTask: ETaskPromise<Array<ISuggestionGroup>>;
|
||||
|
||||
@ -71,17 +82,20 @@ export default class SearchBoxContainer extends Component {
|
||||
*/
|
||||
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;
|
||||
const defaultFacets = facetToParamUrl(transformDefaultsIntoSelections(getFacetDefaultValueForEntity(attributes)));
|
||||
const { dataModels } = this;
|
||||
if (entity) {
|
||||
const dataModelEntity = dataModels.getModel(entity);
|
||||
const { attributes } = dataModelEntity.renderProps.search;
|
||||
const defaultFacets = facetToParamUrl(transformDefaultsIntoSelections(getFacetDefaultValueForEntity(attributes)));
|
||||
|
||||
this.router.transitionTo('search', {
|
||||
queryParams: {
|
||||
entity,
|
||||
keyword: text,
|
||||
page: 1,
|
||||
facets: defaultFacets
|
||||
}
|
||||
});
|
||||
this.router.transitionTo('search', {
|
||||
queryParams: {
|
||||
entity,
|
||||
keyword: text,
|
||||
page: 1,
|
||||
facets: defaultFacets
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,26 +36,38 @@ export default class SearchResult extends Component {
|
||||
/**
|
||||
* Result used in template
|
||||
*/
|
||||
result?: DataModelEntityInstance;
|
||||
result!: DataModelEntityInstance;
|
||||
|
||||
/**
|
||||
* Config for search for this entity
|
||||
*/
|
||||
searchConfig?: IEntityRenderCommonPropsSearch;
|
||||
searchConfig!: IEntityRenderCommonPropsSearch;
|
||||
/**
|
||||
* Will return the name of the entity. By default it will use
|
||||
* 'name' as field to fetch the name, otherwise it should be
|
||||
* specified in 'entityNameField'
|
||||
*/
|
||||
@computed('searchConfig.entityNameField', 'entity')
|
||||
get name(): string | void {
|
||||
const { result, searchConfig = { searchResultEntityFields: { name: 'name' } } } = this;
|
||||
if (result) {
|
||||
const { searchResultEntityFields = { name: 'name' } } = searchConfig;
|
||||
const { name = 'name' } = searchResultEntityFields;
|
||||
get name(): string | undefined {
|
||||
const { result, searchConfig } = this;
|
||||
const { searchResultEntityFields = {} } = searchConfig;
|
||||
const { name = 'name' } = searchResultEntityFields || {};
|
||||
|
||||
return result[name as CompatibleKeysThatReturnString];
|
||||
}
|
||||
return get(result, name as CompatibleKeysThatReturnString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return the description of the entity. By default it will use
|
||||
* 'description' as field to fetch the description, otherwise it should be
|
||||
* specified in 'entityDescriptionField'
|
||||
*/
|
||||
@computed('searchConfig.descriptionField', 'entity')
|
||||
get description(): string | undefined {
|
||||
const { result, searchConfig } = this;
|
||||
const { searchResultEntityFields = {} } = searchConfig;
|
||||
const { description = 'description' } = searchResultEntityFields || {};
|
||||
|
||||
return result[description as CompatibleKeysThatReturnString];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
{{avatars/avatar-image avatar=@owner.avatar}}
|
||||
{{/unless}}
|
||||
|
||||
<a href={{@owner.profile}} rel="noopener" target="blank">
|
||||
<LinkTo @route="user.profile" @model={{@owner.profile}}>
|
||||
{{ownerRecord.userName}}
|
||||
</a>
|
||||
</LinkTo>
|
||||
|
||||
{{#if isOwnerInActive}}
|
||||
|
||||
|
||||
15
datahub-web/packages/data-portal/app/typings/app/search/fields.d.ts
vendored
Normal file
15
datahub-web/packages/data-portal/app/typings/app/search/fields.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Api expected request params for suggestion values autocomplete
|
||||
*/
|
||||
export interface IFieldValuesRequest {
|
||||
input: string;
|
||||
facet?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Api expected response for suggestion values autocomplete
|
||||
*/
|
||||
export interface IFieldValuesResponse {
|
||||
input: string;
|
||||
source: Array<string>;
|
||||
}
|
||||
@ -13,7 +13,7 @@ import { generateGroups } from 'wherehows-web/utils/parsers/autocomplete/steps/g
|
||||
import { arrayReduce } from 'wherehows-web/utils/array';
|
||||
import { createSuggestionsFromError } from 'wherehows-web/utils/parsers/helpers';
|
||||
import { DatasetEntity } from '@datahub/data-models/entity/dataset/dataset-entity';
|
||||
import { DataModelName } from '@datahub/data-models/constants/entity';
|
||||
import { DataModelEntity } from '@datahub/data-models/constants/entity';
|
||||
|
||||
/**
|
||||
* Steps of the grammar process
|
||||
@ -40,7 +40,7 @@ export const grammarProcessingSteps: Array<IGrammarProcessFn> = [
|
||||
*/
|
||||
export const typeaheadQueryProcessor = async (
|
||||
query: string,
|
||||
entity: DataModelName = DatasetEntity.displayName,
|
||||
entity: DataModelEntity = DatasetEntity,
|
||||
steps: Array<IGrammarProcessFn>
|
||||
): Promise<Array<ISuggestionGroup>> => {
|
||||
const initArgs: Pick<ISuggestionBuilder, 'text' | 'entity'> = { text: query, entity };
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import {
|
||||
INodeProcessor,
|
||||
AutocompleteRuleNames,
|
||||
ISuggestionBuilder
|
||||
ISuggestionBuilder,
|
||||
ISuggestion
|
||||
} from 'wherehows-web/utils/parsers/autocomplete/types';
|
||||
import { fetchFacetValue } from 'wherehows-web/utils/parsers/helpers';
|
||||
|
||||
@ -12,17 +13,20 @@ import { fetchFacetValue } from 'wherehows-web/utils/parsers/helpers';
|
||||
export const entityProcessor: INodeProcessor = {
|
||||
[AutocompleteRuleNames.EntityName]: async (builder: ISuggestionBuilder): Promise<ISuggestionBuilder> => {
|
||||
const input = builder.textLastWord || '';
|
||||
const suggestions = await fetchFacetValue('name', input, builder.entity);
|
||||
const nameField = builder.entity.renderProps.search.autocompleteNameField || 'name';
|
||||
const suggestions = await fetchFacetValue(nameField, input, builder.entity);
|
||||
|
||||
return {
|
||||
...builder,
|
||||
datasets: [
|
||||
...builder.datasets,
|
||||
...(suggestions || []).map(entityName => ({
|
||||
title: entityName,
|
||||
text: `${builder.textPrevious}${entityName} `,
|
||||
description: ''
|
||||
}))
|
||||
...(suggestions || []).map(
|
||||
(entityName): ISuggestion => ({
|
||||
title: entityName,
|
||||
text: `${builder.textPrevious}${entityName} `,
|
||||
description: ''
|
||||
})
|
||||
)
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
} from 'wherehows-web/utils/parsers/autocomplete/types';
|
||||
import { dataToString } from 'wherehows-web/utils/parsers/autocomplete/utils';
|
||||
import { ISearchEntityRenderProps } from '@datahub/data-models/types/entity/rendering/search-entity-render-prop';
|
||||
import { DataModelEntity } from '@datahub/data-models/constants/entity';
|
||||
import { fetchFacetValue } from 'wherehows-web/utils/parsers/helpers';
|
||||
|
||||
/**
|
||||
@ -52,7 +51,7 @@ export const facetsProcessor: INodeProcessor = {
|
||||
* When 'name' is expected we just return 'name:' as suggestion
|
||||
*/
|
||||
[AutocompleteRuleNames.FacetName]: (builder: ISuggestionBuilder, ruleState: IState): Promise<ISuggestionBuilder> => {
|
||||
const allFields: Array<ISearchEntityRenderProps> = DataModelEntity[builder.entity].renderProps.search.attributes;
|
||||
const allFields: Array<ISearchEntityRenderProps> = builder.entity.renderProps.search.attributes;
|
||||
const facetName = getFacetNameFromStateRule(ruleState);
|
||||
const fields = allFields.filter(
|
||||
(field): boolean => field.fieldName.indexOf(facetName) >= 0 && field.showInAutoCompletion
|
||||
|
||||
@ -14,7 +14,8 @@ export const generateGroups = (builder: ISuggestionBuilder): ISuggestionBuilder
|
||||
const isEntityNamesEmpty = builder.datasets.length === 0;
|
||||
const expectedEntityName = !!builder.wantedRulesMap[AutocompleteRuleNames.EntityName];
|
||||
const lastWordLength = typeof builder.textLastWord === 'string' ? builder.textLastWord.trim().length : -1;
|
||||
const entityDisplayName = capitalize(builder.entity);
|
||||
const entityModel = builder.entity;
|
||||
const entityDisplayName = capitalize(entityModel.displayName);
|
||||
|
||||
if (isEntityNamesEmpty && expectedEntityName && lastWordLength < 3) {
|
||||
groups.push({
|
||||
|
||||
@ -43,7 +43,7 @@ export type INodeFacetProcessor = Record<
|
||||
* The structure is not intented to be mutated, redux like approach
|
||||
*/
|
||||
export interface ISuggestionBuilder {
|
||||
entity: DataModelEntity['displayName'];
|
||||
entity: DataModelEntity;
|
||||
logicalOperators: Array<ISuggestion>;
|
||||
facetNames: Array<ISuggestion>;
|
||||
datasets: Array<ISuggestion>;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ISuggestionGroup } from 'wherehows-web/utils/parsers/autocomplete/types';
|
||||
import { DataModelEntity, DataModelName } from '@datahub/data-models/constants/entity';
|
||||
import { DataModelEntity } from '@datahub/data-models/constants/entity';
|
||||
import { IFieldValuesResponseV2, FieldValuesRequestV2 } from 'wherehows-web/typings/app/search/fields-v2';
|
||||
import { facetValuesApiEntities } from 'wherehows-web/utils/parsers/autocomplete/utils';
|
||||
|
||||
@ -24,12 +24,13 @@ export const createSuggestionsFromError = (error: string): Array<ISuggestionGrou
|
||||
export const fetchFacetValue = async (
|
||||
facetName: string,
|
||||
facetValue: string,
|
||||
entity: DataModelName
|
||||
entity: DataModelEntity
|
||||
): Promise<Array<string>> => {
|
||||
// otherwise lets invoke api to fetch values
|
||||
let suggestions: Array<string> = [];
|
||||
const { apiName, attributes } = DataModelEntity[entity].renderProps.search;
|
||||
const fieldMeta = attributes.find((attr): boolean => attr.fieldName === facetName);
|
||||
const searchRenderProps = entity.renderProps.search;
|
||||
const searchAttributes = searchRenderProps.attributes;
|
||||
const fieldMeta = searchAttributes.find((attr): boolean => attr.fieldName === facetName);
|
||||
const { minAutocompleteFetchLength } = fieldMeta || { minAutocompleteFetchLength: undefined };
|
||||
const cacheKey = `${facetName}:${facetValue}`;
|
||||
|
||||
@ -38,7 +39,7 @@ export const fetchFacetValue = async (
|
||||
const request: FieldValuesRequestV2<Record<string, string>> = {
|
||||
field: facetName,
|
||||
input: facetValue,
|
||||
type: apiName
|
||||
type: searchRenderProps.apiName
|
||||
};
|
||||
const facetValueReturn: IFieldValuesResponseV2 | undefined = await facetValuesApiEntities({
|
||||
query: facetValue,
|
||||
|
||||
@ -34,32 +34,32 @@ module('Integration | Component | search/search-result', function(hooks) {
|
||||
}
|
||||
});
|
||||
|
||||
test('it renders', async function(assert) {
|
||||
test('it renders', async function(assert): Promise<void> {
|
||||
assert.expect(1);
|
||||
|
||||
const result = createEntity();
|
||||
|
||||
this.setProperties({ fields: [], result });
|
||||
await render(hbs`{{search/search-result
|
||||
result=result.data
|
||||
meta=result.meta
|
||||
resultFields=fields
|
||||
}}`);
|
||||
this.setProperties({ searchConfig: { attributes: [] }, result });
|
||||
await render(hbs`<Search::SearchResult
|
||||
@result={{this.result.data}}
|
||||
@meta={{this.result.meta}}
|
||||
@searchConfig={{this.searchConfig}}
|
||||
/>`);
|
||||
|
||||
assert.ok(find('.search-result'), 'expected component to have a class `search-result`');
|
||||
});
|
||||
|
||||
test('search-result properties', async function(assert) {
|
||||
test('search-result properties', async function(assert): Promise<void> {
|
||||
assert.expect(1);
|
||||
|
||||
const result = createEntity();
|
||||
|
||||
this.setProperties({ fields: [], result });
|
||||
await render(hbs`{{search/search-result
|
||||
result=result.data
|
||||
meta=result.meta
|
||||
resultFields=fields
|
||||
}}`);
|
||||
this.setProperties({ searchConfig: { attributes: [] }, result });
|
||||
await render(hbs`<Search::SearchResult
|
||||
@result={{this.result.data}}
|
||||
@meta={{this.result.meta}}
|
||||
@searchConfig={{this.searchConfig}}
|
||||
/>`);
|
||||
|
||||
const searchResultElement: Element | null = find('.search-result');
|
||||
const title = searchResultElement && searchResultElement.querySelector('.search-result__title');
|
||||
|
||||
@ -1,311 +0,0 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { grammarProcessingSteps, typeaheadQueryProcessor } from 'wherehows-web/utils/parsers/autocomplete';
|
||||
import { startMirage } from 'wherehows-web/initializers/ember-cli-mirage';
|
||||
import { IMirageServer } from '@datahub/utils/types/vendor/ember-cli-mirage-deprecated';
|
||||
import { IMirageWherehows } from 'wherehows-web/typings/ember-cli-mirage';
|
||||
import { ISuggestionGroup } from 'wherehows-web/utils/parsers/autocomplete/types';
|
||||
import { DatasetEntity } from '@datahub/data-models/entity/dataset/dataset-entity';
|
||||
import { DataModelName } from '@datahub/data-models/constants/entity';
|
||||
|
||||
interface ITestSet {
|
||||
entity: DataModelName;
|
||||
description: string;
|
||||
text: string;
|
||||
results: Array<ISuggestionGroup>;
|
||||
}
|
||||
|
||||
const createTests = (server: IMirageWherehows): Array<ITestSet> => {
|
||||
server.create('datasetView', { name: 'platform' });
|
||||
server.create('datasetView', { name: 'pageviewevent' });
|
||||
server.create('platform', { name: 'hive' });
|
||||
server.create('platform', { name: 'mysql' });
|
||||
|
||||
return [
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Initial suggestions',
|
||||
text: '',
|
||||
results: [
|
||||
{
|
||||
groupName: 'Datasets',
|
||||
options: [
|
||||
{
|
||||
disabled: true,
|
||||
text: '',
|
||||
title: 'type at least 3 more characters to see Datasets names'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName: 'Filter By',
|
||||
options: [
|
||||
{
|
||||
description: 'The origin of the dataset, e.g.: origin:PROD',
|
||||
text: 'origin:',
|
||||
title: 'origin:'
|
||||
},
|
||||
{
|
||||
description: 'The name of the dataset, e.g.: name:TRACKING.PageViewEvent',
|
||||
text: 'name:',
|
||||
title: 'name:'
|
||||
},
|
||||
{
|
||||
description: 'The confirmed owners for the dataset, e.g.: owners:sweaver',
|
||||
text: 'owners:',
|
||||
title: 'owners:'
|
||||
},
|
||||
{
|
||||
description: 'The platform of the dataset, e.g.: platform:kafka',
|
||||
text: 'platform:',
|
||||
title: 'platform:'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: '1 Dataset',
|
||||
text: 'pageview',
|
||||
results: [
|
||||
{
|
||||
groupName: 'Datasets',
|
||||
options: [
|
||||
{
|
||||
description: '',
|
||||
text: 'pageviewevent ',
|
||||
title: 'pageviewevent'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Dataset with filter 1',
|
||||
text: 'pageview AND platfo',
|
||||
results: [
|
||||
{
|
||||
groupName: 'Filter By',
|
||||
options: [
|
||||
{
|
||||
description: 'The platform of the dataset, e.g.: platform:kafka',
|
||||
text: 'pageview AND platform:',
|
||||
title: 'platform:'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName: 'Datasets',
|
||||
options: [
|
||||
{
|
||||
description: '',
|
||||
text: 'pageview AND platform ',
|
||||
title: 'platform'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Dataset with filter 2',
|
||||
text: 'pageview AND platform',
|
||||
results: [
|
||||
{
|
||||
groupName: 'Filter By',
|
||||
options: [
|
||||
{
|
||||
description: 'The platform of the dataset, e.g.: platform:kafka',
|
||||
text: 'pageview AND platform:',
|
||||
title: 'platform:'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName: 'Datasets',
|
||||
options: [
|
||||
{
|
||||
description: '',
|
||||
text: 'pageview AND platform ',
|
||||
title: 'platform'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Dataset with filter 3',
|
||||
text: 'pageview AND name:pageview',
|
||||
results: [
|
||||
{
|
||||
groupName: 'Filter By',
|
||||
options: [
|
||||
{
|
||||
text: 'pageview AND name:pageviewevent ',
|
||||
title: 'name:pageviewevent'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// TODO: update tests with sample api response data for "text: 'platform:'", "text: 'platform:my'", and "text: 'origin:co'"
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Dataset with filter platform',
|
||||
text: 'platform:',
|
||||
results: []
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Dataset with filter platform my',
|
||||
text: 'platform:my',
|
||||
results: []
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Dataset with filter fabric',
|
||||
text: 'origin:co',
|
||||
results: []
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Logical Operators',
|
||||
text: 'something AN',
|
||||
results: [
|
||||
{
|
||||
groupName: 'Datasets',
|
||||
options: [
|
||||
{
|
||||
disabled: true,
|
||||
text: '',
|
||||
title: 'type at least 1 more characters to see Datasets names'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName: 'Operators',
|
||||
options: [
|
||||
{
|
||||
text: 'something AND ',
|
||||
title: 'AND'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Logical Operators',
|
||||
text: 'something O',
|
||||
results: [
|
||||
{
|
||||
groupName: 'Datasets',
|
||||
options: [
|
||||
{
|
||||
disabled: true,
|
||||
text: '',
|
||||
title: 'type at least 2 more characters to see Datasets names'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName: 'Operators',
|
||||
options: [
|
||||
{
|
||||
text: 'something OR ',
|
||||
title: 'OR'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Invalid Syntax',
|
||||
text: 'notreallyafacet:',
|
||||
results: []
|
||||
},
|
||||
{
|
||||
entity: DatasetEntity.displayName,
|
||||
description: 'Next things',
|
||||
text: 'something ',
|
||||
results: [
|
||||
{
|
||||
groupName: 'Datasets',
|
||||
options: [
|
||||
{
|
||||
disabled: true,
|
||||
text: '',
|
||||
title: 'type at least 3 more characters to see Datasets names'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName: 'Operators',
|
||||
options: [
|
||||
{
|
||||
text: 'something AND ',
|
||||
title: 'AND'
|
||||
},
|
||||
{
|
||||
text: 'something OR ',
|
||||
title: 'OR'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName: 'Filter By',
|
||||
options: [
|
||||
{
|
||||
description: 'The origin of the dataset, e.g.: origin:PROD',
|
||||
text: 'something origin:',
|
||||
title: 'origin:'
|
||||
},
|
||||
{
|
||||
description: 'The name of the dataset, e.g.: name:TRACKING.PageViewEvent',
|
||||
text: 'something name:',
|
||||
title: 'name:'
|
||||
},
|
||||
{
|
||||
description: 'The confirmed owners for the dataset, e.g.: owners:sweaver',
|
||||
text: 'something owners:',
|
||||
title: 'owners:'
|
||||
},
|
||||
{
|
||||
description: 'The platform of the dataset, e.g.: platform:kafka',
|
||||
text: 'something platform:',
|
||||
title: 'platform:'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
module('Unit | Utility | Autocomplete Suggestions', function(hooks) {
|
||||
let server: IMirageServer;
|
||||
hooks.beforeEach(function() {
|
||||
server = startMirage();
|
||||
});
|
||||
|
||||
hooks.afterEach(function() {
|
||||
server.shutdown();
|
||||
});
|
||||
|
||||
test('Suggestions returns as expected', async function(assert) {
|
||||
const tests = createTests(server);
|
||||
|
||||
assert.expect(tests.length);
|
||||
|
||||
// tests must be resolved in sequence since processing includes some debouncing
|
||||
await tests.reduce(async (previousResolution: Promise<void>, myTest: ITestSet): Promise<void> => {
|
||||
await previousResolution;
|
||||
const result = await typeaheadQueryProcessor(myTest.text, myTest.entity, grammarProcessingSteps);
|
||||
|
||||
assert.deepEqual(result, myTest.results, myTest.description);
|
||||
}, Promise.resolve());
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user