Skip to content

Commit

Permalink
fix(search): add supplemental searching capability (spinnaker#4133)
Browse files Browse the repository at this point in the history
* when keyword search is not performed, removes applications and
clusters from being searched by the the search API to speed up searching
because both can be determined separately.
* application information can be retrieved via the applications API
endpoint - which is cached..
* cluster information for search results can be determined entirely from
server groups.
* adds ability to hydrate search results posthumously to add requested,
extra information.
  • Loading branch information
icfantv authored Sep 23, 2017
1 parent 6e180bd commit a4e3353
Show file tree
Hide file tree
Showing 20 changed files with 403 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { IPromise } from 'angular';
import { StateService } from '@uirouter/angularjs';

import { urlBuilderRegistry } from 'core/navigation/urlBuilder.registry';
import { ApplicationReader, IApplicationSummary } from './service/application.read.service';
import { IPostSearchResultSearcher } from 'core/search/searchResult/PostSearchResultSearcherRegistry';
import { ISearchResult } from 'core/search/search.service';
import { ISearchResultFormatter, searchResultFormatterRegistry } from 'core/search/searchResult/searchResultFormatter.registry';
import { ISearchResultSet } from 'core/search/infrastructure/infrastructureSearch.service';
import { IServerGroupSearchResult } from 'core/search/searchResult/model/IServerGroupSearchResult';

export class ApplicationPostSearchResultSearcher implements IPostSearchResultSearcher<IServerGroupSearchResult> {

private static TYPE = 'applications';

constructor(private $state: StateService, private applicationReader: ApplicationReader) {}

public getPostSearchResults(inputs: IServerGroupSearchResult[]): IPromise<ISearchResultSet[]> {

const names: Set<string> = new Set<string>(inputs.map((result: IServerGroupSearchResult) => result.application));
return this.applicationReader.listApplications(true).then((apps: IApplicationSummary[]) => {

const results: ISearchResult[] = apps.filter((app: IApplicationSummary) => names.has(app.name))
.map((app: IApplicationSummary) => {
return {
accounts: app.accounts,
application: app.name,
displayName: app.name,
href: urlBuilderRegistry.getBuilder(ApplicationPostSearchResultSearcher.TYPE).build({
application: app.name,
type: ApplicationPostSearchResultSearcher.TYPE
}, this.$state),
email: app.email,
provider: app.cloudProviders,
type: ApplicationPostSearchResultSearcher.TYPE
};
});
const formatter: ISearchResultFormatter = searchResultFormatterRegistry.get(ApplicationPostSearchResultSearcher.TYPE);

return [{
id: ApplicationPostSearchResultSearcher.TYPE,
category: ApplicationPostSearchResultSearcher.TYPE,
icon: formatter.icon,
iconClass: '',
order: formatter.order,
results
}];
});
}
}
33 changes: 20 additions & 13 deletions app/scripts/modules/core/src/application/application.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { module } from 'angular';
import { StateService } from '@uirouter/angularjs';

import './ApplicationSearchResultFormatter';
import { APPLICATION_NAV_COMPONENT } from './nav/applicationNav.component';
Expand All @@ -8,19 +9,25 @@ import { APPLICATIONS_COMPONENT } from './applications.component';
import { APPLICATIONS_STATE_PROVIDER } from './applications.state.provider';
import { PERMISSIONS_CONFIGURER_COMPONENT } from './modal/permissionsConfigurer.component';
import { UPSERT_APPLICATION_HELP } from './modal/upsertApplication.help';
import { ApplicationReader } from './service/application.read.service';
import { PostSearchResultSearcherRegistry } from 'core/search/searchResult/PostSearchResultSearcherRegistry';
import { ApplicationPostSearchResultSearcher } from 'core/application/ApplicationPostSearchResultSearcher';

export const APPLICATION_MODULE = 'spinnaker.core.application';
module(APPLICATION_MODULE, [
APPLICATION_STATE_PROVIDER,
APPLICATIONS_STATE_PROVIDER,
APPLICATIONS_COMPONENT,
require('./config/applicationConfig.controller.js'),
require('./modal/createApplication.modal.controller.js'),
require('./modal/pageApplicationOwner.modal.controller.js'),
require('./modal/platformHealthOverride.directive'),
require('./config/appConfig.dataSource'),
APPLICATION_NAV_COMPONENT,
APPLICATION_NAV_SECONDARY_COMPONENT,
PERMISSIONS_CONFIGURER_COMPONENT,
UPSERT_APPLICATION_HELP,
]);
APPLICATION_STATE_PROVIDER,
APPLICATIONS_STATE_PROVIDER,
APPLICATIONS_COMPONENT,
require('./config/applicationConfig.controller.js'),
require('./modal/createApplication.modal.controller.js'),
require('./modal/pageApplicationOwner.modal.controller.js'),
require('./modal/platformHealthOverride.directive'),
require('./config/appConfig.dataSource'),
APPLICATION_NAV_COMPONENT,
APPLICATION_NAV_SECONDARY_COMPONENT,
PERMISSIONS_CONFIGURER_COMPONENT,
UPSERT_APPLICATION_HELP,
])
.run(($state: StateService, applicationReader: ApplicationReader) => {
PostSearchResultSearcherRegistry.register('applications', 'serverGroups', new ApplicationPostSearchResultSearcher($state, applicationReader));
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { Application } from '../application.model';
import { ApplicationDataSource, IDataSourceConfig } from '../service/applicationDataSource';
import { APPLICATION_DATA_SOURCE_REGISTRY, ApplicationDataSourceRegistry } from './applicationDataSource.registry';
import { ROBOT_TO_HUMAN_FILTER } from 'core/presentation/robotToHumanFilter/robotToHuman.filter';
import { INFERRED_APPLICATION_WARNING_SERVICE, InferredApplicationWarningService } from './inferredApplicationWarning.service';
import {
INFERRED_APPLICATION_WARNING_SERVICE,
InferredApplicationWarningService
} from './inferredApplicationWarning.service';

export interface IApplicationDataSourceAttribute {
enabled: string[];
Expand All @@ -27,6 +30,8 @@ export interface IApplicationSummary {

export class ApplicationReader {

private applicationMap: Map<string, IApplicationSummary> = new Map<string, IApplicationSummary>();

public constructor(private $q: IQService,
private $log: ILogService,
private $filter: IFilterService,
Expand All @@ -38,8 +43,15 @@ export class ApplicationReader {
'ngInject';
}

public listApplications(): IPromise<IApplicationSummary[]> {
return this.API.all('applications').useCache().getList();
public listApplications(populateMap = false): IPromise<IApplicationSummary[]> {
return this.API.all('applications').useCache().getList().then((applications: IApplicationSummary[]) => {
if (populateMap) {
const tmpMap: Map<string, IApplicationSummary> = new Map<string, IApplicationSummary>();
applications.forEach((application: IApplicationSummary) => tmpMap.set(application.name, application));
this.applicationMap = tmpMap;
}
return applications;
});
}

public getApplication(name: string): IPromise<Application> {
Expand All @@ -53,6 +65,10 @@ export class ApplicationReader {
});
}

public getApplicationMap(): Map<string, IApplicationSummary> {
return this.applicationMap;
}

private splitAttributes(attributes: any, fields: string[]) {
fields.forEach(field => {
if (attributes[field]) {
Expand All @@ -77,7 +93,7 @@ export class ApplicationReader {

private setDisabledDataSources(application: Application) {
const allDataSources: ApplicationDataSource[] = application.dataSources,
appDataSources: IApplicationDataSourceAttribute = application.attributes.dataSources;
appDataSources: IApplicationDataSourceAttribute = application.attributes.dataSources;
if (!appDataSources) {
allDataSources.filter(ds => ds.optIn).forEach(ds => this.disableDataSource(ds, application));
if (this.inferredApplicationWarningService.isInferredApplication(application)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class ClusterDisplayRenderer extends AbstractBaseResultRenderer<IClusterS
return [
{ key: 'cluster', label: 'Name', cellRenderer: this.hrefCellRenderer },
{ key: 'account', cellRenderer: this.accountCellRenderer },
{ key: 'email', cellRenderer: this.defaultCellRender }
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { IPromise, IQService } from 'angular';
import { StateService } from '@uirouter/angularjs';

import { urlBuilderRegistry } from 'core/navigation/urlBuilder.registry';
import {
ISearchResultFormatter,
searchResultFormatterRegistry
} from 'core/search/searchResult/searchResultFormatter.registry';
import { ISearchResultSet } from 'core/search/infrastructure/infrastructureSearch.service';
import { IClusterSearchResult } from 'core/search/searchResult/model/IClusterSearchResult';
import { IServerGroupSearchResult } from 'core/search/searchResult/model/IServerGroupSearchResult';
import { IPostSearchResultSearcher } from 'core/search/searchResult/PostSearchResultSearcherRegistry';

export class ClusterPostSearchResultSearcher implements IPostSearchResultSearcher<IServerGroupSearchResult> {

private static TYPE = 'clusters';

constructor(private $q: IQService, private $state: StateService) {
}

public getPostSearchResults(inputs: IServerGroupSearchResult[]): IPromise<ISearchResultSet[]> {

const clusters: IClusterSearchResult[] = inputs.map((serverGroup: IServerGroupSearchResult) => {
const { account, application, cluster, detail, region, stack } = serverGroup;
return {
account,
application,
cluster,
displayName: cluster,
href: urlBuilderRegistry.getBuilder(ClusterPostSearchResultSearcher.TYPE).build({
account,
application,
cluster,
stack,
detail,
region,
type: ClusterPostSearchResultSearcher.TYPE
}, this.$state),
provider: serverGroup.provider,
stack,
type: ClusterPostSearchResultSearcher.TYPE
}
});

const formatter: ISearchResultFormatter = searchResultFormatterRegistry.get(ClusterPostSearchResultSearcher.TYPE);
return this.$q.when([{
id: ClusterPostSearchResultSearcher.TYPE,
category: ClusterPostSearchResultSearcher.TYPE,
icon: formatter.icon,
iconClass: '',
order: formatter.order,
results: clusters
}]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { get } from 'lodash';

import { ApplicationReader, IApplicationSummary } from 'core/application/service/application.read.service';
import { IClusterSearchResult } from 'core/search/searchResult/model/IClusterSearchResult';
import { ISearchResultHydrator } from 'core/search/searchResult/SearchResultHydratorRegistry';

export class ClusterSearchResultHydrator implements ISearchResultHydrator<IClusterSearchResult> {

private static FIELDS: string[] = ['email'];

constructor(private applicationReader: ApplicationReader) {}

public hydrate(target: IClusterSearchResult[]): void {

const appMap: Map<string, IApplicationSummary> = this.applicationReader.getApplicationMap();
target.forEach((cluster: IClusterSearchResult) => {

const app: IApplicationSummary = appMap.get(cluster.application);

// pluck all fields from the application and set on the cluster
const hydrationData: { [key: string]: string } =
ClusterSearchResultHydrator.FIELDS.reduce((data: { [key: string]: string }, field: string) => {
data[field] = get<string>(app, field);
return data;
}, {});
Object.assign(cluster, hydrationData);
});
}
}
16 changes: 13 additions & 3 deletions app/scripts/modules/core/src/cluster/cluster.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { module } from 'angular';
import { IQService, module } from 'angular';
import { StateService } from '@uirouter/angularjs';

import './ClusterSearchResultFormatter';
import { CLUSTER_ALLCLUSTERSGROUPINGS } from './allClustersGroupings.component';
import { ON_DEMAND_CLUSTER_PICKER_COMPONENT } from './onDemand/onDemandClusterPicker.component';
import './ClusterSearchResultFormatter';
import { PostSearchResultSearcherRegistry } from 'core/search/searchResult/PostSearchResultSearcherRegistry';
import { SearchResultHydratorRegistry } from 'core/search/searchResult/SearchResultHydratorRegistry';
import { ClusterPostSearchResultSearcher } from './ClusterPostSearchResultSearcher';
import { ClusterSearchResultHydrator } from './ClusterSearchResultHydrator';
import { ApplicationReader } from 'core/application/service/application.read.service';

export const CLUSTER_MODULE = 'spinnaker.core.cluster';

module(CLUSTER_MODULE, [
require('./allClusters.controller.js'),
CLUSTER_ALLCLUSTERSGROUPINGS,
ON_DEMAND_CLUSTER_PICKER_COMPONENT,
]);
])
.run(($q: IQService, $state: StateService, applicationReader: ApplicationReader) => {
PostSearchResultSearcherRegistry.register('clusters', 'serverGroups', new ClusterPostSearchResultSearcher($q, $state));
SearchResultHydratorRegistry.register('clusters', new ClusterSearchResultHydrator(applicationReader));
});
3 changes: 2 additions & 1 deletion app/scripts/modules/core/src/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { PAGE_TITLE_MODULE } from './pageTitle/pageTitle.module';
import { PIPELINE_TEMPLATE_MODULE } from './pipeline/config/templates/pipelineTemplate.module';
import { REACT_MODULE } from './reactShims';
import { REGION_MODULE } from './region/region.module';
import { SERVERGROUP_MODULE } from './serverGroup/serverGroup.module';
import { SUBNET_MODULE } from './subnet/subnet.module';
import { WHATS_NEW_MODULE } from './whatsNew/whatsNew.module';
import { WIDGETS_MODULE } from './widgets/widgets.module';
Expand Down Expand Up @@ -112,7 +113,7 @@ module(CORE_MODULE, [

require('./search/search.module'),
require('./securityGroup/securityGroup.module'),
require('./serverGroup/serverGroup.module'),
SERVERGROUP_MODULE,
SUBNET_MODULE,

require('./task/task.module'),
Expand Down
Loading

0 comments on commit a4e3353

Please sign in to comment.