Misc. bug fixes related to search and person entity display attributes, and lost picture url for person entity

This commit is contained in:
Seyi Adebajo 2020-02-10 18:51:15 -08:00 committed by Seyi Adebajo
parent d8c594aeb9
commit 06d19d2958
19 changed files with 128 additions and 476 deletions

View File

@ -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 { IEntityRenderProps } from '@datahub/data-models/types/entity/rendering/entity-render-props';
import { DataModelEntity } from '@datahub/data-models/constants/entity'; import { DataModelEntity } from '@datahub/data-models/constants/entity';
import { IPersonEntityEditableProperties } from '@datahub/data-models/types/entity/person/props'; 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 { readPerson, saveEditablePersonalInfo } from '@datahub/data-models/api/person/entity';
import { alias, not } from '@ember/object/computed'; import { alias, not } from '@ember/object/computed';
@ -164,6 +164,11 @@ export class PersonEntity extends BaseEntity<ICorpUserInfo> {
@alias('entity.info.email') @alias('entity.info.email')
email!: string; 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. * 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, { return saveEditablePersonalInfo(this.urn, {
teams: props.teamTags, teams: props.teamTags,
aboutMe: props.focusArea, aboutMe: props.focusArea,
skills: props.skills skills: props.skills,
pictureLink: this.pictureLink
}); });
} }
} }

View File

@ -58,7 +58,7 @@ export const getRenderProps = (): IEntityRenderProps => {
searchResultEntityFields: { searchResultEntityFields: {
description: 'title', description: 'title',
pictureUrl: 'editableInfo.pictureLink', pictureUrl: 'editableInfo.pictureLink',
name: 'username' name: 'info.fullName'
}, },
showFacets: false, showFacets: false,
placeholder: 'Search for People...', placeholder: 'Search for People...',

View File

@ -70,13 +70,6 @@ export default class EntityDeprecation extends Component {
*/ */
centeredDate: Date = this.selectedDate; 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, * 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 * 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; 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 * The earliest date a user can select as a decommission date
* @type {Date} * @type {Date}
@ -116,13 +101,6 @@ export default class EntityDeprecation extends Component {
@reads('deprecationNote') @reads('deprecationNote')
deprecationNoteAlias!: EntityDeprecation['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 * Invokes the save action with the updated values for deprecated status, decommission time, and
* deprecation note. The actual save functionality should be handled by * deprecation note. The actual save functionality should be handled by
@ -155,13 +133,4 @@ export default class EntityDeprecation extends Component {
onDecommissionDateChange(decommissionTime: Date): void { onDecommissionDateChange(decommissionTime: Date): void {
set(this, 'decommissionTime', new Date(decommissionTime).getTime()); 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');
}
} }

View File

@ -80,27 +80,13 @@
{{if decommissionTime (concat 'Will be decommissioned on ' (moment-format decommissionTime 'MMMM DD YYYY')) ''}} {{if decommissionTime (concat 'Will be decommissioned on ' (moment-format decommissionTime 'MMMM DD YYYY')) ''}}
</strong> </strong>
</p> </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}} {{/if}}
<div class="entity-deprecation__actions"> <div class="entity-deprecation__actions">
<button <button
type="submit" type="submit"
class="nacho-button--large nacho-button--secondary" class="nacho-button--large nacho-button--secondary"
disabled={{and isDeprecatedAlias (or (not decommissionTime) (not isDeprecationAcknowledged))}}> disabled={{and isDeprecatedAlias (not decommissionTime)}}>
Update Deprecation Status Update Deprecation Status
</button> </button>
</div> </div>

View File

@ -1,6 +1,6 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-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'; import hbs from 'htmlbars-inline-precompile';
const findInput = (selector: string): HTMLInputElement => { const findInput = (selector: string): HTMLInputElement => {
@ -67,7 +67,6 @@ module('Integration | Component | entity-deprecation', function(hooks): void {
test('decommissionTime', async function(assert): Promise<void> { test('decommissionTime', async function(assert): Promise<void> {
let isDisabled; let isDisabled;
assert.expect(3);
this.setProperties({ this.setProperties({
...defaultProperties, ...defaultProperties,
@ -83,58 +82,5 @@ module('Integration | Component | entity-deprecation', function(hooks): void {
isDisabled = findInput('.entity-deprecation__actions [type=submit]').disabled; isDisabled = findInput('.entity-deprecation__actions [type=submit]').disabled;
assert.ok(isDisabled, 'submit button is 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');
}); });
}); });

View File

@ -8,6 +8,8 @@ export interface ICorpUserEditableInfo {
teams: Array<string>; teams: Array<string>;
// A self-assigned list of skills that the person claims to own // A self-assigned list of skills that the person claims to own
skills: Array<string>; skills: Array<string>;
// Reference to the picture URL for the person
pictureLink: string;
} }
/** /**

View File

@ -26,6 +26,7 @@ import { NotificationEvent } from '@datahub/utils/constants/notifications';
import { PersonEntity } from '@datahub/data-models/entity/person/person-entity'; import { PersonEntity } from '@datahub/data-models/entity/person/person-entity';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import { ETaskPromise } from '@datahub/utils/types/concurrency'; import { ETaskPromise } from '@datahub/utils/types/concurrency';
import DataModelsService from '@datahub/data-models/services/data-models';
type Comparator = -1 | 0 | 1; type Comparator = -1 | 0 | 1;
@ -89,6 +90,12 @@ export default class DatasetAuthors extends Component {
@service('user-lookup') @service('user-lookup')
userLookup: UserLookup; 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 * 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 * 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} * @returns {OwnerWithAvatarRecord}
*/ */
datasetAuthorsOwnersAugmentedWithAvatars = (owner: IOwner): OwnerWithAvatarRecord => { datasetAuthorsOwnersAugmentedWithAvatars = (owner: IOwner): OwnerWithAvatarRecord => {
const { avatarProperties } = this; const { avatarProperties, dataModels } = this;
const PersonEntityClass = dataModels.getModel('people') as typeof PersonEntity;
return { return {
owner, owner,
avatar: avatarProperties avatar: avatarProperties
? makeAvatar(avatarProperties)(owner) ? makeAvatar(avatarProperties)(owner)
: { imageUrl: '', imageUrlFallback: '/assets/images/default_avatar.png' }, : { imageUrl: '', imageUrlFallback: '/assets/images/default_avatar.png' },
profile: PersonEntity.profileLinkFromUsername(owner.userName) profile: PersonEntityClass.urnFromUsername(owner.userName)
}; };
}; };

View File

@ -16,6 +16,7 @@ import { facetToParamUrl } from 'wherehows-web/utils/api/search/search';
import RouterService from '@ember/routing/router-service'; import RouterService from '@ember/routing/router-service';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import { ETaskPromise } from '@datahub/utils/types/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 * Search box container that handle all the data part for
@ -29,6 +30,9 @@ export default class SearchBoxContainer extends Component {
@service @service
search: SearchService; search: SearchService;
@service('data-models')
dataModels!: DataModelsService;
@alias('search.entity') @alias('search.entity')
entity?: DataModelName; entity?: DataModelName;
@ -44,9 +48,9 @@ export default class SearchBoxContainer extends Component {
*/ */
@computed('entity') @computed('entity')
get dataModelEntity(): DataModelEntity | undefined { get dataModelEntity(): DataModelEntity | undefined {
const { entity } = this; const { entity, dataModels } = this;
if (entity) { if (entity && dataModels) {
return DataModelEntity[entity]; return dataModels.getModel(entity);
} }
return; return;
} }
@ -58,8 +62,15 @@ export default class SearchBoxContainer extends Component {
* @returns {(IterableIterator<Promise<Array<ISuggestionGroup>>>)} * @returns {(IterableIterator<Promise<Array<ISuggestionGroup>>>)}
* @memberof SearchBoxContainer * @memberof SearchBoxContainer
*/ */
@task(function*(query: string = '', entity?: DataModelName): IterableIterator<Promise<Array<ISuggestionGroup>>> { @task(function*(
return yield typeaheadQueryProcessor(query, entity, grammarProcessingSteps); 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>>; onSearchInputTask: ETaskPromise<Array<ISuggestionGroup>>;
@ -71,17 +82,20 @@ export default class SearchBoxContainer extends Component {
*/ */
onSearch(text: string = '', entity: DataModelName = DatasetEntity.displayName): void { onSearch(text: string = '', entity: DataModelName = DatasetEntity.displayName): void {
// entity (dropdown value) might be different than this.entity (Page that you are in) // entity (dropdown value) might be different than this.entity (Page that you are in)
const dataModelEntity = DataModelEntity[entity]; const { dataModels } = this;
const { attributes } = dataModelEntity.renderProps.search; if (entity) {
const defaultFacets = facetToParamUrl(transformDefaultsIntoSelections(getFacetDefaultValueForEntity(attributes))); const dataModelEntity = dataModels.getModel(entity);
const { attributes } = dataModelEntity.renderProps.search;
const defaultFacets = facetToParamUrl(transformDefaultsIntoSelections(getFacetDefaultValueForEntity(attributes)));
this.router.transitionTo('search', { this.router.transitionTo('search', {
queryParams: { queryParams: {
entity, entity,
keyword: text, keyword: text,
page: 1, page: 1,
facets: defaultFacets facets: defaultFacets
} }
}); });
}
} }
} }

View File

@ -36,26 +36,38 @@ export default class SearchResult extends Component {
/** /**
* Result used in template * Result used in template
*/ */
result?: DataModelEntityInstance; result!: DataModelEntityInstance;
/** /**
* Config for search for this entity * Config for search for this entity
*/ */
searchConfig?: IEntityRenderCommonPropsSearch; searchConfig!: IEntityRenderCommonPropsSearch;
/** /**
* Will return the name of the entity. By default it will use * Will return the name of the entity. By default it will use
* 'name' as field to fetch the name, otherwise it should be * 'name' as field to fetch the name, otherwise it should be
* specified in 'entityNameField' * specified in 'entityNameField'
*/ */
@computed('searchConfig.entityNameField', 'entity') @computed('searchConfig.entityNameField', 'entity')
get name(): string | void { get name(): string | undefined {
const { result, searchConfig = { searchResultEntityFields: { name: 'name' } } } = this; const { result, searchConfig } = this;
if (result) { const { searchResultEntityFields = {} } = searchConfig;
const { searchResultEntityFields = { name: 'name' } } = searchConfig; const { name = 'name' } = searchResultEntityFields || {};
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];
} }
/** /**

View File

@ -4,9 +4,9 @@
{{avatars/avatar-image avatar=@owner.avatar}} {{avatars/avatar-image avatar=@owner.avatar}}
{{/unless}} {{/unless}}
<a href={{@owner.profile}} rel="noopener" target="blank"> <LinkTo @route="user.profile" @model={{@owner.profile}}>
{{ownerRecord.userName}} {{ownerRecord.userName}}
</a> </LinkTo>
{{#if isOwnerInActive}} {{#if isOwnerInActive}}

View 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>;
}

View File

@ -13,7 +13,7 @@ import { generateGroups } from 'wherehows-web/utils/parsers/autocomplete/steps/g
import { arrayReduce } from 'wherehows-web/utils/array'; import { arrayReduce } from 'wherehows-web/utils/array';
import { createSuggestionsFromError } from 'wherehows-web/utils/parsers/helpers'; import { createSuggestionsFromError } from 'wherehows-web/utils/parsers/helpers';
import { DatasetEntity } from '@datahub/data-models/entity/dataset/dataset-entity'; 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 * Steps of the grammar process
@ -40,7 +40,7 @@ export const grammarProcessingSteps: Array<IGrammarProcessFn> = [
*/ */
export const typeaheadQueryProcessor = async ( export const typeaheadQueryProcessor = async (
query: string, query: string,
entity: DataModelName = DatasetEntity.displayName, entity: DataModelEntity = DatasetEntity,
steps: Array<IGrammarProcessFn> steps: Array<IGrammarProcessFn>
): Promise<Array<ISuggestionGroup>> => { ): Promise<Array<ISuggestionGroup>> => {
const initArgs: Pick<ISuggestionBuilder, 'text' | 'entity'> = { text: query, entity }; const initArgs: Pick<ISuggestionBuilder, 'text' | 'entity'> = { text: query, entity };

View File

@ -1,7 +1,8 @@
import { import {
INodeProcessor, INodeProcessor,
AutocompleteRuleNames, AutocompleteRuleNames,
ISuggestionBuilder ISuggestionBuilder,
ISuggestion
} from 'wherehows-web/utils/parsers/autocomplete/types'; } from 'wherehows-web/utils/parsers/autocomplete/types';
import { fetchFacetValue } from 'wherehows-web/utils/parsers/helpers'; import { fetchFacetValue } from 'wherehows-web/utils/parsers/helpers';
@ -12,17 +13,20 @@ import { fetchFacetValue } from 'wherehows-web/utils/parsers/helpers';
export const entityProcessor: INodeProcessor = { export const entityProcessor: INodeProcessor = {
[AutocompleteRuleNames.EntityName]: async (builder: ISuggestionBuilder): Promise<ISuggestionBuilder> => { [AutocompleteRuleNames.EntityName]: async (builder: ISuggestionBuilder): Promise<ISuggestionBuilder> => {
const input = builder.textLastWord || ''; 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 { return {
...builder, ...builder,
datasets: [ datasets: [
...builder.datasets, ...builder.datasets,
...(suggestions || []).map(entityName => ({ ...(suggestions || []).map(
title: entityName, (entityName): ISuggestion => ({
text: `${builder.textPrevious}${entityName} `, title: entityName,
description: '' text: `${builder.textPrevious}${entityName} `,
})) description: ''
})
)
] ]
}; };
} }

View File

@ -7,7 +7,6 @@ import {
} from 'wherehows-web/utils/parsers/autocomplete/types'; } from 'wherehows-web/utils/parsers/autocomplete/types';
import { dataToString } from 'wherehows-web/utils/parsers/autocomplete/utils'; import { dataToString } from 'wherehows-web/utils/parsers/autocomplete/utils';
import { ISearchEntityRenderProps } from '@datahub/data-models/types/entity/rendering/search-entity-render-prop'; 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'; 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 * When 'name' is expected we just return 'name:' as suggestion
*/ */
[AutocompleteRuleNames.FacetName]: (builder: ISuggestionBuilder, ruleState: IState): Promise<ISuggestionBuilder> => { [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 facetName = getFacetNameFromStateRule(ruleState);
const fields = allFields.filter( const fields = allFields.filter(
(field): boolean => field.fieldName.indexOf(facetName) >= 0 && field.showInAutoCompletion (field): boolean => field.fieldName.indexOf(facetName) >= 0 && field.showInAutoCompletion

View File

@ -14,7 +14,8 @@ export const generateGroups = (builder: ISuggestionBuilder): ISuggestionBuilder
const isEntityNamesEmpty = builder.datasets.length === 0; const isEntityNamesEmpty = builder.datasets.length === 0;
const expectedEntityName = !!builder.wantedRulesMap[AutocompleteRuleNames.EntityName]; const expectedEntityName = !!builder.wantedRulesMap[AutocompleteRuleNames.EntityName];
const lastWordLength = typeof builder.textLastWord === 'string' ? builder.textLastWord.trim().length : -1; 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) { if (isEntityNamesEmpty && expectedEntityName && lastWordLength < 3) {
groups.push({ groups.push({

View File

@ -43,7 +43,7 @@ export type INodeFacetProcessor = Record<
* The structure is not intented to be mutated, redux like approach * The structure is not intented to be mutated, redux like approach
*/ */
export interface ISuggestionBuilder { export interface ISuggestionBuilder {
entity: DataModelEntity['displayName']; entity: DataModelEntity;
logicalOperators: Array<ISuggestion>; logicalOperators: Array<ISuggestion>;
facetNames: Array<ISuggestion>; facetNames: Array<ISuggestion>;
datasets: Array<ISuggestion>; datasets: Array<ISuggestion>;

View File

@ -1,5 +1,5 @@
import { ISuggestionGroup } from 'wherehows-web/utils/parsers/autocomplete/types'; 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 { IFieldValuesResponseV2, FieldValuesRequestV2 } from 'wherehows-web/typings/app/search/fields-v2';
import { facetValuesApiEntities } from 'wherehows-web/utils/parsers/autocomplete/utils'; import { facetValuesApiEntities } from 'wherehows-web/utils/parsers/autocomplete/utils';
@ -24,12 +24,13 @@ export const createSuggestionsFromError = (error: string): Array<ISuggestionGrou
export const fetchFacetValue = async ( export const fetchFacetValue = async (
facetName: string, facetName: string,
facetValue: string, facetValue: string,
entity: DataModelName entity: DataModelEntity
): Promise<Array<string>> => { ): Promise<Array<string>> => {
// otherwise lets invoke api to fetch values // otherwise lets invoke api to fetch values
let suggestions: Array<string> = []; let suggestions: Array<string> = [];
const { apiName, attributes } = DataModelEntity[entity].renderProps.search; const searchRenderProps = entity.renderProps.search;
const fieldMeta = attributes.find((attr): boolean => attr.fieldName === facetName); const searchAttributes = searchRenderProps.attributes;
const fieldMeta = searchAttributes.find((attr): boolean => attr.fieldName === facetName);
const { minAutocompleteFetchLength } = fieldMeta || { minAutocompleteFetchLength: undefined }; const { minAutocompleteFetchLength } = fieldMeta || { minAutocompleteFetchLength: undefined };
const cacheKey = `${facetName}:${facetValue}`; const cacheKey = `${facetName}:${facetValue}`;
@ -38,7 +39,7 @@ export const fetchFacetValue = async (
const request: FieldValuesRequestV2<Record<string, string>> = { const request: FieldValuesRequestV2<Record<string, string>> = {
field: facetName, field: facetName,
input: facetValue, input: facetValue,
type: apiName type: searchRenderProps.apiName
}; };
const facetValueReturn: IFieldValuesResponseV2 | undefined = await facetValuesApiEntities({ const facetValueReturn: IFieldValuesResponseV2 | undefined = await facetValuesApiEntities({
query: facetValue, query: facetValue,

View File

@ -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); assert.expect(1);
const result = createEntity(); const result = createEntity();
this.setProperties({ fields: [], result }); this.setProperties({ searchConfig: { attributes: [] }, result });
await render(hbs`{{search/search-result await render(hbs`<Search::SearchResult
result=result.data @result={{this.result.data}}
meta=result.meta @meta={{this.result.meta}}
resultFields=fields @searchConfig={{this.searchConfig}}
}}`); />`);
assert.ok(find('.search-result'), 'expected component to have a class `search-result`'); 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); assert.expect(1);
const result = createEntity(); const result = createEntity();
this.setProperties({ fields: [], result }); this.setProperties({ searchConfig: { attributes: [] }, result });
await render(hbs`{{search/search-result await render(hbs`<Search::SearchResult
result=result.data @result={{this.result.data}}
meta=result.meta @meta={{this.result.meta}}
resultFields=fields @searchConfig={{this.searchConfig}}
}}`); />`);
const searchResultElement: Element | null = find('.search-result'); const searchResultElement: Element | null = find('.search-result');
const title = searchResultElement && searchResultElement.querySelector('.search-result__title'); const title = searchResultElement && searchResultElement.querySelector('.search-result__title');

View File

@ -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());
});
});