diff --git a/awx/ui/client/src/activity-stream/activitystream.controller.js b/awx/ui/client/src/activity-stream/activitystream.controller.js index b7333b0bab..73c8f9c732 100644 --- a/awx/ui/client/src/activity-stream/activitystream.controller.js +++ b/awx/ui/client/src/activity-stream/activitystream.controller.js @@ -9,47 +9,69 @@ * @name controllers.function:Activity Stream * @description This controller controls the activity stream. */ -export default ['$scope', '$state', 'subTitle', 'Stream', 'GetTargetTitle', - 'StreamList', 'Dataset', - function activityStreamController($scope, $state, subTitle, Stream, - GetTargetTitle, list, Dataset) { +export default ['$scope', '$state', 'subTitle', 'GetTargetTitle', + 'StreamList', 'Dataset', '$rootScope', 'ShowDetail', 'BuildDescription', + function activityStreamController($scope, $state, subTitle, GetTargetTitle, + list, Dataset, $rootScope, ShowDetail, BuildDescription) { - init(); - initOmitSmartTags(); + // search init + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - function init() { - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + // subTitle is passed in via a resolve on the route. If there is no subtitle + // generated in the resolve then we go get the targets generic title. - // subTitle is passed in via a resolve on the route. If there is no subtitle - // generated in the resolve then we go get the targets generic title. + // Get the streams sub-title based on the target. This scope variable is leveraged + // when we define the activity stream list. Specifically it is included in the list + // title. + $scope.streamSubTitle = subTitle ? subTitle : GetTargetTitle($state.params.target); - // Get the streams sub-title based on the target. This scope variable is leveraged - // when we define the activity stream list. Specifically it is included in the list - // title. - $scope.streamSubTitle = subTitle ? subTitle : GetTargetTitle($state.params.target); + $rootScope.flashMessage = null; - // Open the stream - Stream({ - scope: $scope + $scope.refreshStream = function () { + $state.go('.', null, {reload: true}); + }; + + $scope.showDetail = function (id) { + ShowDetail({ + scope: $scope, + activity_id: id }); + }; + + if($scope.activities && $scope.activities.length > 0) { + buildUserAndDescription(); } - // Specification of smart-tags omission from the UI is done in the route/state init. - // A limitation is that this specficiation is static and the key for which to be omitted from - // the smart-tags must be known at that time. - // In the case of activity stream, we won't to dynamically ommit the resource for which we are - // displaying the activity stream for. i.e. 'project', 'credential', etc. - function initOmitSmartTags() { - let defaults, route = _.find($state.$current.path, (step) => { - return step.params.hasOwnProperty('activity_search'); + $scope.$watch('activities', function(){ + // Watch for future update to scope.activities (like page change, column sort, search, etc) + buildUserAndDescription(); + }); + + function buildUserAndDescription(){ + $scope.activities.forEach(function(activity, i) { + // build activity.user + if ($scope.activities[i].summary_fields.actor) { + $scope.activities[i].user = "" + + $scope.activities[i].summary_fields.actor.username + ""; + } else { + $scope.activities[i].user = 'system'; + } + // build description column / action text + BuildDescription($scope.activities[i]); + }); - if (route && $state.params.target !== undefined) { - defaults = route.params.activity_search.config.value; - defaults[$state.params.target] = null; - } } + + const route = _.find($state.$current.path, (step) => { + return step.params.hasOwnProperty('activity_search'); + }); + let defaultParams = angular.copy(route.params.activity_search.config.value); + + defaultParams.or__object1__in = $state.params.activity_search.or__object1__in ? $state.params.activity_search.or__object1__in : defaultParams.or__object1__in; + defaultParams.or__object2__in = $state.params.activity_search.or__object2__in ? $state.params.activity_search.or__object2__in : defaultParams.or__object2__in; + + $scope[`${list.iterator}_default_params`] = defaultParams; } ]; diff --git a/awx/ui/client/src/activity-stream/factories/stream.factory.js b/awx/ui/client/src/activity-stream/factories/stream.factory.js deleted file mode 100644 index 43bef1de06..0000000000 --- a/awx/ui/client/src/activity-stream/factories/stream.factory.js +++ /dev/null @@ -1,50 +0,0 @@ -export default - function Stream($rootScope, $state, BuildDescription, ShowDetail) { - return function (params) { - - var scope = params.scope; - - $rootScope.flashMessage = null; - - // descriptive title describing what AS is showing - scope.streamTitle = (params && params.title) ? params.title : null; - - scope.refreshStream = function () { - $state.go('.', null, {reload: true}); - }; - - scope.showDetail = function (id) { - ShowDetail({ - scope: scope, - activity_id: id - }); - }; - - if(scope.activities && scope.activities.length > 0) { - buildUserAndDescription(); - } - - scope.$watch('activities', function(){ - // Watch for future update to scope.activities (like page change, column sort, search, etc) - buildUserAndDescription(); - }); - - function buildUserAndDescription(){ - scope.activities.forEach(function(activity, i) { - // build activity.user - if (scope.activities[i].summary_fields.actor) { - scope.activities[i].user = "" + - scope.activities[i].summary_fields.actor.username + ""; - } else { - scope.activities[i].user = 'system'; - } - // build description column / action text - BuildDescription(scope.activities[i]); - - }); - } - - }; - } - - Stream.$inject = ['$rootScope', '$state', 'BuildDescription', 'ShowDetail']; diff --git a/awx/ui/client/src/activity-stream/main.js b/awx/ui/client/src/activity-stream/main.js index b1e625e2d7..383bab8d11 100644 --- a/awx/ui/client/src/activity-stream/main.js +++ b/awx/ui/client/src/activity-stream/main.js @@ -11,7 +11,6 @@ import streamDetailModal from './streamDetailModal/main'; import BuildAnchor from './factories/build-anchor.factory'; import BuildDescription from './factories/build-description.factory'; import ShowDetail from './factories/show-detail.factory'; -import Stream from './factories/stream.factory'; import GetTargetTitle from './get-target-title.factory'; import ModelToBasePathKey from './model-to-base-path-key.factory'; import ActivityDetailForm from './activity-detail.form'; @@ -23,7 +22,6 @@ export default angular.module('activityStream', [streamDetailModal.name]) .factory('BuildAnchor', BuildAnchor) .factory('BuildDescription', BuildDescription) .factory('ShowDetail', ShowDetail) - .factory('Stream', Stream) .factory('GetTargetTitle', GetTargetTitle) .factory('ModelToBasePathKey', ModelToBasePathKey) .factory('ActivityDetailForm', ActivityDetailForm) diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index ba8aad5dc5..cb0b6087fc 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -34,8 +34,10 @@ function QuerysetService ($q, Rest, ProcessErrors, $rootScope, Wait, DjangoSearc return defer.promise; }, replaceDefaultFlags (value) { - value = value.toString().replace(/__icontains_DEFAULT/g, "__icontains"); - value = value.toString().replace(/__search_DEFAULT/g, "__search"); + if (value) { + value = value.toString().replace(/__icontains_DEFAULT/g, "__icontains"); + value = value.toString().replace(/__search_DEFAULT/g, "__search"); + } return value; }, diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index ba481cec6e..ad14dd5344 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -235,7 +235,7 @@ function SmartSearchController ( }; $scope.clearAllTerms = () => { - const cleared = _.cloneDeep(defaults); + const cleared = _(defaults).omit(_.isNull).value(); delete cleared.page; diff --git a/awx/ui/test/spec/karma.spec.js b/awx/ui/test/spec/karma.spec.js index ae2dc8899d..104f8d9ba8 100644 --- a/awx/ui/test/spec/karma.spec.js +++ b/awx/ui/test/spec/karma.spec.js @@ -7,24 +7,25 @@ const webpackConfig = require('./webpack.spec'); module.exports = config => { config.set({ + basePath: '../..', autoWatch: true, colors: true, browsers: ['Chrome', 'Firefox'], frameworks: ['jasmine'], reporters: ['progress', 'junit'], files:[ - './polyfills.js', - path.join(SRC_PATH, '**/*.html'), - path.join(SRC_PATH, 'vendor.js'), + 'test/spec/polyfills.js', + 'client/src/vendor.js', path.join(NODE_MODULES, 'angular-mocks/angular-mocks.js'), path.join(SRC_PATH, 'app.js'), - '**/*-test.js', + 'client/src/**/*.html', + 'test/spec/**/*-test.js', ], preprocessors: { - [path.join(SRC_PATH, '**/*.html')]: 'html2js', - [path.join(SRC_PATH, 'vendor.js')]: 'webpack', + 'client/src/vendor.js': 'webpack', [path.join(SRC_PATH, 'app.js')]: 'webpack', - '**/*-test.js': 'webpack' + 'client/src/**/*.html': 'html2js', + 'test/spec/**/*-test.js': 'webpack' }, webpack: webpackConfig, webpackMiddleware: { diff --git a/awx/ui/test/spec/smart-search/smart-search.directive-test.js b/awx/ui/test/spec/smart-search/smart-search.directive-test.js index 59a701e9bc..afc6df33ed 100644 --- a/awx/ui/test/spec/smart-search/smart-search.directive-test.js +++ b/awx/ui/test/spec/smart-search/smart-search.directive-test.js @@ -1,7 +1,8 @@ 'use strict'; -xdescribe('Directive: Smart Search', () => { +describe('Directive: Smart Search', () => { let $scope, + $q, template, element, dom, @@ -9,119 +10,91 @@ xdescribe('Directive: Smart Search', () => { $state = {}, $stateParams, GetBasePath, - QuerySet; + QuerySet, + ConfigService = {}, + i18n, + $transitions, + translateFilter; beforeEach(angular.mock.module('shared')); beforeEach(angular.mock.module('SmartSearchModule', ($provide) => { - QuerySet = jasmine.createSpyObj('QuerySet', ['decodeParam']); + QuerySet = jasmine.createSpyObj('QuerySet', [ + 'decodeParam', + 'search', + 'stripDefaultParams', + 'createSearchTagsFromQueryset', + 'initFieldset' + ]); QuerySet.decodeParam.and.callFake((key, value) => { return `${key.split('__').join(':')}:${value}`; }); - GetBasePath = jasmine.createSpy('GetBasePath'); + QuerySet.stripDefaultParams.and.returnValue([]); + QuerySet.createSearchTagsFromQueryset.and.returnValue([]); + $transitions = jasmine.createSpyObj('$transitions', [ + 'onSuccess' + ]); + $transitions.onSuccess.and.returnValue({}); + + ConfigService = jasmine.createSpyObj('ConfigService', [ + 'getConfig' + ]); + + GetBasePath = jasmine.createSpy('GetBasePath'); + translateFilter = jasmine.createSpy('translateFilter'); + i18n = jasmine.createSpy('i18n'); + $state = jasmine.createSpyObj('$state', ['go']); + + $provide.value('ConfigService', ConfigService); $provide.value('QuerySet', QuerySet); $provide.value('GetBasePath', GetBasePath); $provide.value('$state', $state); + $provide.value('i18n', { '_': (a) => { return a; } }); + $provide.value('translateFilter', translateFilter); })); - beforeEach(angular.mock.inject(($templateCache, _$rootScope_, _$compile_) => { + beforeEach(angular.mock.inject(($templateCache, _$rootScope_, _$compile_, _$q_) => { + $q = _$q_; + $compile = _$compile_; + $scope = _$rootScope_.$new(); + + ConfigService.getConfig.and.returnValue($q.when({})); + QuerySet.search.and.returnValue($q.when({})); + + QuerySet.initFieldset.and.callFake(() => { + var deferred = $q.defer(); + deferred.resolve({ + models: { + mock: { + base: {} + } + }, + options: { + data: null + } + }); + return deferred.promise; + }); + // populate $templateCache with directive.templateUrl at test runtime, template = window.__html__['client/src/shared/smart-search/smart-search.partial.html']; $templateCache.put('/static/partials/shared/smart-search/smart-search.partial.html', template); - - $compile = _$compile_; - $scope = _$rootScope_.$new(); })); - describe('initializing tags', () => { - beforeEach(() => { - QuerySet.initFieldset = function() { - return { - then: function() { - return; - } - }; - }; - }); - // some defaults like page_size and page will always be provided - // but should be squashed if initialized with default values - it('should not create tags', () => { + describe('clear all', () => { + it('should revert search back to non-null defaults and remove page', () => { $state.$current = { - params: { - mock_search: { - config: { - value: { - page_size: '20', - order_by: '-finished', - page: '1' - } - } - } - } - }; - $state.params = { - mock_search: { - page_size: '20', - order_by: '-finished', - page: '1' - } - }; - dom = angular.element(` - `); - element = $compile(dom)($scope); - $scope.$digest(); - expect($('.SmartSearch-tagContainer', element).length).toEqual(0); - }); - // set one possible default (order_by) with a custom value, but not another default (page_size) - it('should create an order_by tag, but not a page_size tag', () => { - $state.$current = { - params: { - mock_search: { - config: { - value: { - page_size: '20', - order_by: '-finished' - } - } - } - } - }; - $state.params = { - mock_search: { - page_size: '20', - order_by: 'name' - } - }; - dom = angular.element(` - `); - element = $compile(dom)($scope); - $scope.$digest(); - expect($('.SmartSearch-tagContainer', element).length).toEqual(1); - expect($('.SmartSearch-tagContainer .SmartSearch-name', element)[0].innerText).toEqual('order_by:name'); - }); - // set many possible defaults and many non-defaults - page_size and page shouldn't generate tags, even when non-default values are set - it('should create an order_by tag, name tag, description tag - but not a page_size or page tag', () => { - $state.$current = { - params: { - mock_search: { - config: { - value: { - page_size: '20', - order_by: '-finished', - page: '1' + path: { + mock: { + params: { + mock_search: { + config: { + value: { + page_size: '20', + order_by: '-finished', + page: '1', + some_null_param: null + } + } } } } @@ -136,6 +109,9 @@ xdescribe('Directive: Smart Search', () => { name_icontains: 'ansible' } }; + $scope.list = { + iterator: 'mock' + }; dom = angular.element(` { iterator="mock" collection="dataset" search-tags="searchTags" + list="list" > `); element = $compile(dom)($scope); $scope.$digest(); - expect($('.SmartSearch-tagContainer', element).length).toEqual(3); + const scope = element.isolateScope(); + scope.clearAllTerms(); + expect(QuerySet.search).toHaveBeenCalledWith('mock', {page_size: '20',order_by: '-finished',}); }); }); - - describe('removing tags', () => { - // assert a default value is still provided after a custom tag is removed - xit('should revert to state-defined order_by when order_by tag is removed', () => {}); - }); - - describe('accessing model', () => { - xit('should retrieve cached model OPTIONS from localStorage', () => {}); - xit('should call QuerySet service to retrieve unstored model OPTIONS', () => {}); - }); }); diff --git a/awx/ui/test/spec/webpack.spec.js b/awx/ui/test/spec/webpack.spec.js index 6ed0f049ad..099d6a9964 100644 --- a/awx/ui/test/spec/webpack.spec.js +++ b/awx/ui/test/spec/webpack.spec.js @@ -1,8 +1,6 @@ -const path = require('path'); - const webpack = require('webpack'); const merge = require('webpack-merge'); -const base = require(path.resolve(__dirname, '../..', 'build/webpack.base')); +const base = require('../../build/webpack.base'); const STATIC_URL = '/static/';