Skip to content

Commit

Permalink
New gobal fast property search view. (spinnaker#3005)
Browse files Browse the repository at this point in the history
Updating tslint to inclued console.log and console.warn.
  • Loading branch information
zanthrash authored Nov 15, 2016
1 parent 3b00dc2 commit 7d70a33
Show file tree
Hide file tree
Showing 20 changed files with 1,154 additions and 112 deletions.
Original file line number Diff line number Diff line change
@@ -1,65 +1,242 @@
'use strict';

let angular = require('angular');
import _ from 'lodash';

module.exports = angular
.module('spinnaker.netflix.fastProperties.controller', [
require('angular-ui-router'),
require('core/application/service/applications.read.service.js'),
require('core/cache/deckCacheFactory.js'),
require('../fastProperty.read.service.js'),
require('core/cache/deckCacheFactory.js')
])
.controller('FastPropertiesController', function ($filter, applicationReader, settings) {
var vm = this;
.controller('FastPropertiesController', function ($scope, $filter, $state, $stateParams, $location, applicationReader, settings, fastPropertyReader) {
let vm = this;
let filterNames = ['app','env', 'region', 'stack', 'cluster'];

vm.isOn = settings.feature.fastProperty;
vm.fetchingProperties = false;

vm.applicationsLoaded = false;
$scope.filters = {list: []};

vm.sortModel = { key: 'name' };
vm.clearFilters = () => {
$scope.filters.list = [];
vm.updateFilter();
};

$scope.createFilterTag = function(tag) {
if(tag) {
return {
label: tag.label,
value: tag.value,
clear: () => {
$scope.filters.list.splice(_.findIndex($scope.filters.list, {label: tag.label, value: tag.value}), 1);
vm.updateFilter();
}
};
}
};


/*
* Convert the url filter params to tag objects
*/
let paramToTagList = (label) => _.flatten([$stateParams[label]]).reduce((acc, val) => {
if (!_.isEmpty(val)) {
acc.push($scope.createFilterTag({label:label, value: val}));
}
return acc;
}, []);

let createTagsFromUrlParams = () => {
return _.flatten(filterNames.map(paramToTagList));
};

$scope.filters.list = _.uniqWith(_.flatten([createTagsFromUrlParams()]), (a, b) => a.label === b.label && a.value === b.value);

$scope.$watchCollection('filters', () => {
if(vm.propertiesList) {
vm.updateFilter();
}
});

vm.searchTerm = $stateParams.q || '';

vm.filterNames = {
SHOW_ALL: 'showall',
GLOBAL: 'global'
};

vm.groupNames = {
NONE: 'none',
APP: 'app',
PROPERTY: 'property'
};


vm.isPropertiesListEmpty = () => _.isEmpty(vm.propertiesList);

// FILTER SETTINGS
vm.filterName = $stateParams.filter || vm.filterNames.SHOW_ALL;
vm.groupName = $stateParams.group || vm.groupNames.NONE;

vm.selectedFilterIs = (filterName) => {
return vm.filterName === filterName;
};

vm.setFilterTo = (filterName) => {
vm.filterName = filterName;
vm.filterAndGroup(vm.searchResults);
};

vm.setGroupTo = (groupByName) => {
vm.groupName = groupByName;
vm.filterAndGroup(vm.searchResults);
};

vm.updateFilter = () => {
vm.filterAndGroup(vm.searchResults);
};


vm.selectedGroupIs = (groupByName) => {
return vm.groupName === groupByName;
};

vm.applicationFilter = '';
let allPass = (listOfPredicate) => {
return (property) => listOfPredicate.every(predicate => predicate(property));
};

let globalFilterPredicate = (property) => property.appId.includes('All (Global)');

let normalizeForNone = (keys) => {
return keys.map((key) => {
return key === 'none' ? '' : key;
});
};

vm.filterObject = {
name: vm.applicationFilter,
let scopeFilterPredicateFactory = (scopeLabel) => {
return (property) => {
let scopeAttrList = $scope.filters.list
.filter((filter) => filter.label === scopeLabel)
.map((filter) => filter.value);
return scopeAttrList.length ? normalizeForNone(scopeAttrList).includes(property.scope[scopeLabel]) : true;
};
};

vm.pagination = {
currentPage: 1,
itemsPerPage: 12,
maxSize: 12

let predicateList = filterNames.map(name => scopeFilterPredicateFactory(name));

let filters = {
showall: (propertiesList) => angular.copy(propertiesList).filter(allPass([...predicateList])),
global: (propertiesList) => angular.copy(propertiesList).filter(allPass([globalFilterPredicate, ...predicateList])),
};

let groupByFn = {
none: (propertiesList) => _.sortBy(angular.copy(propertiesList), (prop) => prop.key.toLowerCase()),
app: (propertiesList) => {
let groups = _.groupBy(angular.copy(propertiesList), 'appId' );
for (let key in groups) {
groups[key] = _.sortBy(groups[key], (prop) => prop.key.toLowerCase());
}
return groups;
},
property: (propertiesList) => _.groupBy(angular.copy(propertiesList), 'key')
};

vm.filterAndGroup = (propertyList) => {
vm.propertiesList = groupByFn[vm.groupName]( filters[vm.filterName](propertyList) );
setStateParams();
};

let setStateParams = () => {
$location.search('q', vm.searchTerm);
$location.search('group', vm.groupName);
$location.search('filter', vm.filterName);

filterNames.forEach((name) => {
$location.search(name, $scope.filters.list.filter(tag => tag.label === name).map(tag => tag.value));
});
};

vm.filteredResultPage = function() {
return vm.resultPage(vm.filter(vm.applications));
};

vm.resultPage = function(applications) {
var start = (vm.pagination.currentPage - 1) * vm.pagination.itemsPerPage;
var end = vm.pagination.currentPage * vm.pagination.itemsPerPage;
return applications.slice(start, end);
vm.search = _.debounce(function () {

$location.search('q', vm.searchTerm);

if(vm.searchTerm.length) {
vm.fetchingProperties = true;
vm.propertiesList = undefined;
vm.searchError = undefined;

fastPropertyReader.search(vm.searchTerm).then((data) => {
vm.searchResults = data.propertiesList.map((fp) => {
fp.scope = extractFastPropertyScopeFromId(fp.propertyId);
fp.appId = fp.appId || 'All (Global)';
return fp;
});
return vm.searchResults;
}).then((searchResults) => {
vm.filterAndGroup(searchResults);
}).catch(() => {
vm.propertiesList = undefined;
vm.searchError = `No results found for: ${vm.searchTerm}`;
}).finally(() => {
vm.fetchingProperties = false;
});
} else {
vm.propertiesList = undefined;
vm.fetchingProperties = false;
}

}, 500);


let extractFastPropertyScopeFromId = (propertyId) => {
// Property Id is a pipe delimited key of that has most of the scope info in it.
// $NAME|$APPLICATION|$ENVIRONMENT|$REGION||$STACK|$COUNTRY(|cluster=$CLUSTER)
if (propertyId) {
let items = propertyId.split('|');
return {
key: items[0],
app: items[1],
env: items[2],
region: items[3],
stack: items[5],
cluster: items[7] ? items[7].split('=')[1] : '',
};
}
return {};
};

vm.filter = function(applications) {
return sortApplications(filterApplications(applications), vm.sortModel.key);
};

vm.filteredCount = function() {
return filterApplications(vm.applications).length;
vm.toggleExtraFilters = () => {
vm.extraFiltersOpened = !vm.extraFiltersOpened;
setStateParams();
};

var filterApplications = function(applications) {
var filtered = $filter('anyFieldFilter')(applications, {name: vm.applicationFilter});
return filtered;
vm.getRegions = () => {
if(vm.searchResults) {
return _.compact(_.uniq(vm.searchResults.map((fp) => {
return fp.scope.region;
})));
}
};

var sortApplications = function (applicationList, orderKey) {
var sorted = $filter('orderBy')(applicationList, orderKey);
return sorted;
vm.getStacks = () => {
if(vm.searchResults) {
return _.compact(_.uniq(vm.searchResults.map((fp) => {
return fp.scope.stack;
})));
}
};

applicationReader.listApplications().then(function(applications) {
vm.applicationsLoaded = true;
vm.applications = applications;
});

vm.search();


return vm;
});

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as _ from 'lodash';

class FastPropertyFilterDirective implements ng.IDirective {

scope: any = {
'properties': '=',
'filters': '=',
'createFilterTag': '='
};
template: string = `<input type="search" class="form-control" placeholder="Filters: type '?'">`;
restrict: string = 'E';
fields: string[] = ['app', 'env', 'region', 'stack', 'cluster'];

link(scope: any, el: any) {
let input = el.children('input');

let getScopeAttributeList = (scopeName: string) => {
let results = ['none'].concat(<string[]> _.uniq(scope.properties.map((prop: any) => prop.scope[scopeName])));
return results;
};

let textcompleteComponents = this.fields.map((field) => {
return {
id: field,
match: new RegExp(`${field}:(\\w*|\\s*)$`),
index: 1,
search: (term: string, callback: any) => {
callback(getScopeAttributeList(field).filter((attr: string) => {
if (attr.includes(term) ) {
return attr;
}
}));
},
replace: (attr: string): string => {
let copy = scope.filters.list.splice(0);
let tagBody = {label: field, value: attr};
copy.push(scope.createFilterTag(tagBody));
scope.filters.list = _.uniqWith(copy, (a: any, b: any) => a.label === b.label && a.value === b.value);
scope.$apply();
return '';
}
};
});

input.textcomplete(_.flatten([
textcompleteComponents,
{
match: /(\s*|\w*)\?(\s*|\w*|')$/,
index: 2,
search: (term: string, callback: any) => {
callback($.map(this.fields, (word: string) => {
return word.indexOf(term) > -1 ? word : null;
}));
},
replace: (word: string) => {
return `${word}:`;
}
},
{
match: /(^|\b)(\w{1,})$/,
search: (term: string, callback: any): any => {
callback($.map(this.fields, (word: string) => {
return word.indexOf(term) > -1 ? word : null;
}));
},
replace: (word: string): string => {
return `${word}:`;
}
},

]));
}
}

class DirectiveFactory {

public static getFactoryFor<T extends ng.IDirective>(classType: Function): ng.IDirectiveFactory {
let factory = (...args: any[]): T => {
let directive = <any>classType;
return new directive(args);
};

factory.$inject = classType.$inject;
return factory;
}
}

const moduleName = 'spinnaker.netflix.fastPropertyFilter.directive';

angular
.module(moduleName, [])
.directive('fastPropertyFilter', DirectiveFactory.getFactoryFor(FastPropertyFilterDirective));

export default moduleName;
Loading

0 comments on commit 7d70a33

Please sign in to comment.