initial search integration

This commit is contained in:
Jake McDermott
2018-03-04 16:51:03 -05:00
parent 13162ca33a
commit 7acc99cf15
6 changed files with 254 additions and 11 deletions

View File

@@ -150,3 +150,52 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
// Search ---------------------------------------------------------------------------------
@at-jobz-top-search-key: @at-space-2x;
@at-jobz-bottom-search-key: @at-space-3x;
.jobz-searchKeyPaneContainer {
margin-top: @at-jobz-top-search-key;
margin-bottom: @at-jobz-bottom-search-key;
}
.jobz-searchKeyPane {
// background-color: @at-gray-f6;
background-color: @login-notice-bg;
color: @login-notice-text;
border-radius: @at-border-radius;
border: 1px solid @at-gray-b7;
// color: @at-gray-848992;
padding: 6px @at-padding-input 6px @at-padding-input;
}
.jobz-searchClearAll {
font-size: 10px;
padding-bottom: @at-space;
}
.jobz-Button-searchKey {
.at-mixin-Button();
background-color: @at-blue;
border-color: at-color-button-border-default;
color: @at-white;
&:hover, &:active {
color: @at-white;
background-color: @at-blue-hover;
box-shadow: none;
}
&:focus {
color: @at-white;
}
}
.jobz-tagz {
margin-top: @at-space;
display: flex;
width: 100%;
flex-wrap: wrap;
}

View File

@@ -9,6 +9,8 @@ let page;
let render; let render;
let scroll; let scroll;
let resource; let resource;
let $state;
let qs;
let chain; let chain;
@@ -19,7 +21,9 @@ function JobsIndexController (
_render_, _render_,
_$scope_, _$scope_,
_$compile_, _$compile_,
_$q_ _$q_,
_$state_,
_qs_,
) { ) {
vm = this || {}; vm = this || {};
@@ -59,6 +63,23 @@ function JobsIndexController (
const stream = false; // TODO: Set in route const stream = false; // TODO: Set in route
chain = $q.resolve(); chain = $q.resolve();
// search
$state = _$state_;
qs = _qs_;
vm.searchValue = '';
vm.searchRejected = null;
vm.searchKey = false;
vm.searchKeyExamples = searchKeyExamples;
vm.searchKeyFields = searchKeyFields;
vm.clearSearch = clearSearch;
vm.search = search;
vm.toggleSearchKey = toggleSearchKey;
vm.removeSearchTag = removeSearchTag;
vm.searchTags = getSearchTags(getCurrentQueryset());
render.requestAnimationFrame(() => init()); render.requestAnimationFrame(() => init());
} }
@@ -318,6 +339,62 @@ function toggle (uuid, menu) {
lines.removeClass('hidden'); lines.removeClass('hidden');
} }
//
// Search
//
const searchReloadOptions = { reload: true, inherit: false };
const searchKeyExamples = ['id:>1', 'task:set', 'created:>=2000-01-01'];
const searchKeyFields = ['changed', 'failed', 'host_name', 'stdout', 'task', 'role', 'playbook', 'play'];
function toggleSearchKey () {
vm.searchKey = !vm.searchKey;
}
function getCurrentQueryset() {
const { job_event_search } = $state.params;
return qs.decodeArr(job_event_search);
}
function getSearchTags (queryset) {
return qs.createSearchTagsFromQueryset(queryset)
.filter(tag => !tag.startsWith('event'))
.filter(tag => !tag.startsWith('-event'))
.filter(tag => !tag.startsWith('page_size'))
.filter(tag => !tag.startsWith('order_by'));
}
function removeSearchTag (index) {
const searchTerm = vm.searchTags[index];
const currentQueryset = getCurrentQueryset();
const modifiedQueryset = qs.removeTermsFromQueryset(currentQueryset, searchTerm);
vm.searchTags = getSearchTags(modifiedQueryset);
$state.params.job_event_search = qs.encodeArr(modifiedQueryset);
$state.transitionTo($state.current, $state.params, searchReloadOptions);
}
function search () {
const searchInputQueryset = qs.getSearchInputQueryset(vm.searchValue);
const currentQueryset = getCurrentQueryset();
const modifiedQueryset = qs.mergeQueryset(currentQueryset, searchInputQueryset);
vm.searchTags = getSearchTags(modifiedQueryset);
$state.params.job_event_search = qs.encodeArr(modifiedQueryset);
$state.transitionTo($state.current, $state.params, searchReloadOptions);
}
function clearSearch () {
vm.searchTags = [];
$state.params.job_event_search = '';
$state.transitionTo($state.current, $state.params, searchReloadOptions);
} }
JobsIndexController.$inject = [ JobsIndexController.$inject = [
@@ -327,7 +404,9 @@ JobsIndexController.$inject = [
'JobRenderService', 'JobRenderService',
'$scope', '$scope',
'$compile', '$compile',
'$q' '$q',
'$state',
'QuerySet',
]; ];
module.exports = JobsIndexController; module.exports = JobsIndexController;

View File

@@ -6,6 +6,7 @@ import Controller from '~features/output/index.controller';
import PageService from '~features/output/page.service'; import PageService from '~features/output/page.service';
import RenderService from '~features/output/render.service'; import RenderService from '~features/output/render.service';
import ScrollService from '~features/output/scroll.service'; import ScrollService from '~features/output/scroll.service';
import SearchKeyDirective from '~features/output/search-key.directive';
const Template = require('~features/output/index.view.html'); const Template = require('~features/output/index.view.html');
@@ -15,8 +16,8 @@ const PAGE_LIMIT = 3;
const PAGE_SIZE = 100; const PAGE_SIZE = 100;
const WS_PREFIX = 'ws'; const WS_PREFIX = 'ws';
function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJob, $stateParams) { function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJob, $stateParams, qs, Wait) {
const { id, type } = $stateParams; const { id, type, job_event_search } = $stateParams;
let Resource; let Resource;
let related = 'events'; let related = 'events';
@@ -43,14 +44,20 @@ function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJ
return null; return null;
} }
const params = { page_size: PAGE_SIZE, order_by: 'start_line' };
if (job_event_search) {
const searchParams = qs.encodeQuerysetObject(qs.decodeArr(job_event_search));
Object.assign(params, searchParams);
}
Wait('start');
return new Resource('get', id) return new Resource('get', id)
.then(model => model.extend(related, { .then(model => model.extend(related, {
pageCache: PAGE_CACHE, pageCache: PAGE_CACHE,
pageLimit: PAGE_LIMIT, pageLimit: PAGE_LIMIT,
params: { params,
page_size: PAGE_SIZE,
order_by: 'start_line'
}
})) }))
.then(model => { .then(model => {
return { return {
@@ -67,7 +74,9 @@ function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJ
pageLimit: PAGE_LIMIT pageLimit: PAGE_LIMIT
} }
}; };
}); })
.catch(({ data, status }) => qs.error(data, status))
.finally(() => Wait('stop'));
} }
function resolveWebSocketConnection (SocketService, $stateParams) { function resolveWebSocketConnection (SocketService, $stateParams) {
@@ -131,8 +140,8 @@ function getWebSocketResource (type) {
function JobsRun ($stateRegistry) { function JobsRun ($stateRegistry) {
const state = { const state = {
name: 'jobz', name: 'jobz',
url: '/jobz/:type/:id', url: '/jobz/:type/:id?job_event_search',
route: '/jobz/:type/:id', route: '/jobz/:type/:id?job_event_search',
data: { data: {
activityStream: true, activityStream: true,
activityStreamTarget: 'jobs' activityStreamTarget: 'jobs'
@@ -152,6 +161,8 @@ function JobsRun ($stateRegistry) {
'SystemJobModel', 'SystemJobModel',
'WorkflowJobModel', 'WorkflowJobModel',
'$stateParams', '$stateParams',
'QuerySet',
'Wait',
resolveResource resolveResource
], ],
ncyBreadcrumb: [ ncyBreadcrumb: [
@@ -179,6 +190,7 @@ angular
.service('JobStrings', Strings) .service('JobStrings', Strings)
.service('JobPageService', PageService) .service('JobPageService', PageService)
.service('JobScrollService', ScrollService) .service('JobScrollService', ScrollService)
.directive('atSearchKey', SearchKeyDirective)
.run(JobsRun); .run(JobsRun);
export default MODULE_NAME; export default MODULE_NAME;

View File

@@ -7,6 +7,51 @@
<div class="col-md-8"> <div class="col-md-8">
<at-panel class="at-Stdout"> <at-panel class="at-Stdout">
<!-- search ============================================================================================================= -->
<form ng-submit="vm.search()">
<div class="input-group">
<input type="text"
class="form-control at-Input"
ng-class="{ 'at-Input--rejected': vm.searchRejected }"
ng-model="vm.searchValue"
ng-attr-placeholder="SEARCH"
ng-disabled="vm.searchDisabled">
<span class="input-group-btn">
<button class="btn at-ButtonHollow--default at-Input-button"
ng-click="vm.search()"
ng-disabled="vm.searchDisabled"
type="button">
<i class="fa fa-search"></i>
</button>
<button class="btn jobz-Button-searchKey"
ng-if="vm.searchKey"
ng-disabled="vm.searchDisabled"
ng-click="vm.toggleSearchKey()"
type="button"> key
</button>
<button class="btn at-ButtonHollow--default at-Input-button"
ng-if="!vm.searchKey"
ng-disabled="vm.searchDisabled"
ng-click="vm.toggleSearchKey()"
type="button"> key
</button>
</span>
</div>
</form>
<div class="jobz-tagz">
<div class="LabelList-tagContainer" ng-repeat="tag in vm.searchTags track by $index">
<div class="LabelList-tag LabelList-tag--deletable"><span class="LabelList-name">{{ tag }}</span></div>
<div class="LabelList-deleteContainer" ng-click="vm.removeSearchTag($index)">
<i class="fa fa-times LabelList-tagDelete"></i>
</div>
</div>
<div><a href class="jobz-searchClearAll" ng-click="vm.clearSearch()" ng-show="!(vm.searchTags | isEmpty)">CLEAR ALL</a></div>
</div>
<at-search-key ng-show="vm.searchKey" fields="vm.searchKeyFields" examples="vm.searchKeyExamples"></at-search-key>
<!-- ==================================================================================================================== -->
<div class="at-Stdout-menuTop"> <div class="at-Stdout-menuTop">
<div class="pull-left" ng-click="vm.expand()"> <div class="pull-left" ng-click="vm.expand()">
<i class="at-Stdout-menuIcon fa" <i class="at-Stdout-menuIcon fa"

View File

@@ -0,0 +1,38 @@
const templateUrl = require('~features/output/search-key.partial.html');
function atSearchKeyLink (scope, el, attrs, controllers) {
const [atSearchKeyController] = controllers;
atSearchKeyController.init(scope);
}
function AtSearchKeyController () {
const vm = this || {};
vm.init = scope => {
vm.examples = scope.examples || [];
vm.fields = scope.fields || [];
vm.relatedFields = scope.relatedFields || [];
}
}
AtSearchKeyController.$inject = ['$scope'];
function atSearchKey () {
return {
templateUrl,
restrict: 'E',
require: ['atSearchKey'],
controllerAs: 'vm',
link: atSearchKeyLink,
controller: AtSearchKeyController,
scope: {
examples: '=',
fields: '=',
relatedFields: '=',
},
};
}
export default atSearchKey;

View File

@@ -0,0 +1,20 @@
<!-- todo: styling, css etc. - disposition according to project lib conventions -->
<div class="jobz-searchKeyPaneContainer">
<div class="jobz-searchKeyPane">
<div class="SmartSearch-keyRow">
<div class="SmartSearch-examples">
<div class="SmartSearch-examples--title"><b>EXAMPLES:</b></div>
<div class="SmartSearch-examples--search" ng-repeat="tag in vm.examples"> {{ tag }}</div>
</div>
</div>
<div class="SmartSearch-keyRow">
<b>FIELDS:</b>
<span ng-repeat="field in vm.fields">{{ field }}<span ng-if="!$last">, </span></span>
</div>
<div class="SmartSearch-keyRow">
<b>ADDITIONAL INFORMATION:</b>
For additional information on advanced search search syntax please see the Ansible Tower
<a ng-attr-href="undefined" target="_blank">documentation</a>.
</div>
</div>
</div>