Pārlūkot izejas kodu

Implemented advanced search for the helpdesk page.

jalbr74 6 gadi atpakaļ
vecāks
revīzija
8ca318ccac

+ 48 - 7
client/src/modules/helpdesk/helpdesk-search-base.component.ts

@@ -29,17 +29,23 @@ import LocalStorageService from '../../services/local-storage.service';
 import PromiseService from '../../services/promise.service';
 import {IHelpDeskService} from '../../services/helpdesk.service';
 import IPwmService from '../../services/pwm.service';
+import {IAdvancedSearchConfig} from '../../services/base-config.service';
 
 let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html');
 let recentVerificationsDialogTemplateUrl = require('./recent-verifications-dialog.template.html');
 
 export default abstract class HelpDeskSearchBaseComponent {
+    advancedSearch = false;
+    advancedSearchTags = [];
+    advancedSearchEnabled: boolean;
+    advancedSearchMaxRows: number;
     columnConfiguration: any;
     errorMessage: string;
     inputDebounce: number;
     protected pendingRequests: IPromise<any>[] = [];
     photosEnabled: boolean;
     query: string;
+    queries = [{key: null, value: ''}];
     searchMessage: string;
     searchResult: SearchResult;
     searchTextLocalStorageKey: string;
@@ -75,6 +81,12 @@ export default abstract class HelpDeskSearchBaseComponent {
             this.verificationsEnabled = verificationsEnabled;
         });
 
+        this.configService.advancedSearchConfig().then((advancedSearchConfig: IAdvancedSearchConfig) => {
+            this.advancedSearchEnabled = advancedSearchConfig.enabled;
+            this.advancedSearchTags = advancedSearchConfig.attributes;
+            this.advancedSearchMaxRows = advancedSearchConfig.maxRows;
+        });
+
         // Once <ias-search-box> from ng-ias allows the autofocus attribute, we can remove this code
         this.$timeout(() => {
             document.getElementsByTagName('input')[0].focus();
@@ -102,6 +114,7 @@ export default abstract class HelpDeskSearchBaseComponent {
 
     protected clearSearch(): void {
         this.query = null;
+        this.queries = [{key: null, value: ''}];
         this.searchResult = null;
         this.clearErrorMessage();
         this.clearSearchMessage();
@@ -111,13 +124,25 @@ export default abstract class HelpDeskSearchBaseComponent {
     protected fetchSearchData(): IPromise<void | SearchResult> {
         this.abortPendingRequests();
         this.searchResult = null;
+        let promise;
+
+        if (this.advancedSearch) {
+            if (!this.queries || (this.queries.length === 1 && !this.queries[0].key)) {
+                this.clearSearch();
+                return null;
+            }
+
+            promise = this.helpDeskService.advancedSearch(this.queries);
+        }
+        else {
+            if (!this.query) {
+                this.clearSearch();
+                return null;
+            }
 
-        if (!this.query) {
-            this.clearSearch();
-            return null;
+            promise = this.helpDeskService.search(this.query);
         }
 
-        let promise = this.helpDeskService.search(this.query);
         this.pendingRequests.push(promise);
 
         return promise
@@ -156,15 +181,19 @@ export default abstract class HelpDeskSearchBaseComponent {
         this.$state.go(state, { query: this.query });
     }
 
+    private initiateSearch() {
+        this.clearSearchMessage();
+        this.clearErrorMessage();
+        this.fetchData();
+    }
+
     private onSearchTextChange(newValue: string, oldValue: string): void {
         if (newValue === oldValue) {
             return;
         }
 
         this.storeSearchText();
-        this.clearSearchMessage();
-        this.clearErrorMessage();
-        this.fetchData();
+        this.initiateSearch();
     }
 
     protected abortPendingRequests() {
@@ -211,6 +240,13 @@ export default abstract class HelpDeskSearchBaseComponent {
         }
     }
 
+    removeSearchTag(tagIndex: number): void {
+        this.queries.splice(tagIndex, 1);
+    }
+    addSearchTag(): void {
+        this.queries.push({key: null, value: ''});
+    }
+
     protected selectPerson(person: IPerson): void {
         this.IasDialogService
             .open({
@@ -235,6 +271,11 @@ export default abstract class HelpDeskSearchBaseComponent {
         this.localStorageService.setItem(this.searchTextLocalStorageKey, this.query || '');
     }
 
+    toggleAdvancedSearch(): void {
+        this.clearSearch();
+        this.advancedSearch = !this.advancedSearch;
+    }
+
     protected toggleView(state: string): void {
         this.storeSearchView(state);
         this.storeSearchText();

+ 35 - 1
client/src/modules/helpdesk/helpdesk-search-cards.component.html

@@ -21,10 +21,22 @@
   -->
 
 <div class="ias-header">
-    <h2 id="page-content-title" translate="Title_Helpdesk">Help Desk</h2>
+    <h2 id="page-content-title" ng-if="!$ctrl.advancedSearch" translate="Title_Helpdesk">Help Desk</h2>
+    <h2 id="page-content-title" ng-if="$ctrl.advancedSearch">Help Desk Advanced Search</h2>
+
     <ias-search-box ng-model="$ctrl.query" ng-model-options="{debounce: $ctrl.inputDebounce}"
+                    ng-if="!$ctrl.advancedSearch"
                     placeholder="{{'Placeholder_Search' | translate}}">
     </ias-search-box>
+    <ias-button id="advanced-search-icon" class="ias-icon-button" ng-click="$ctrl.toggleAdvancedSearch()"
+                ng-if="!$ctrl.advancedSearch && $ctrl.advancedSearchEnabled"
+                ng-attr-title="{{ 'Title_AdvancedSearch' | translate }}">
+        <ias-icon class="ias-selected" icon="search_advanced"></ias-icon>
+    </ias-button>
+    <ias-button id="close-advanced-search-icon" class="ias-icon-button" ng-click="$ctrl.toggleAdvancedSearch()"
+                ng-if="$ctrl.advancedSearch" ng-attr-title="{{ 'Button_Close' | translate }}">
+        <ias-icon class="ias-selected" icon="close_thin"></ias-icon>
+    </ias-button>
     <ias-button class="verifications-button ias-cta" ng-if="$ctrl.verificationsEnabled"
                 ng-click="$ctrl.showVerifications()">{{ 'Button_Verifications' | translate }}</ias-button>
 
@@ -42,6 +54,28 @@
     </ias-button>
 </div>
 
+<div class="advanced-search-container" ng-if="$ctrl.advancedSearch">
+    <div class="attribute-row" ng-repeat="query in $ctrl.queries">
+        <select ng-model="query.key">
+            <option value="" selected disabled>{{ 'Display_SelectAttribute' | translate }}</option>
+            <option ng-repeat="tag in $ctrl.advancedSearchTags" ng-attr-value="{{tag.attribute}}">{{tag.label}}</option>
+        </select>
+        <input ng-model="query.value" autocomplete="off">
+        <ias-button class="ias-icon-button" ng-click="$ctrl.removeSearchTag($index)" ng-if="$index > 0"
+                    ng-attr-title="{{ 'Button_Remove' | translate }}">
+            <ias-icon icon="close_thin"></ias-icon>
+        </ias-button>
+    </div>
+    <ias-button id="add-attribute-row" class="ias-icon-button" ng-click="$ctrl.addSearchTag()"
+                ng-if="$ctrl.queries.length < $ctrl.advancedSearchMaxRows"
+                ng-attr-title="{{ 'Button_AddSearchAttribute' | translate }}">
+        <ias-icon icon="new_thin"></ias-icon>
+    </ias-button>
+    <ias-button id="advanced-search-button" ng-click="$ctrl.initiateSearch()"
+                ng-bind="'Placeholder_Search' | translate">
+    </ias-button>
+</div>
+
 <div class="search-info-container">
     <div class="search-info" ng-class="{'loading': !$ctrl.getMessage()}"
          ng-if="$ctrl.loading || $ctrl.searchMessage || $ctrl.errorMessage"

+ 35 - 1
client/src/modules/helpdesk/helpdesk-search-table.component.html

@@ -21,10 +21,22 @@
   -->
 
 <div class="ias-header">
-    <h2 id="page-content-title" translate="Title_Helpdesk">Help Desk</h2>
+    <h2 id="page-content-title" ng-if="!$ctrl.advancedSearch" translate="Title_Helpdesk">Help Desk</h2>
+    <h2 id="page-content-title" ng-if="$ctrl.advancedSearch">Help Desk Advanced Search</h2>
+
     <ias-search-box ng-model="$ctrl.query" ng-model-options="{debounce: $ctrl.inputDebounce}"
+                    ng-if="!$ctrl.advancedSearch"
                     placeholder="{{'Placeholder_Search' | translate}}">
     </ias-search-box>
+    <ias-button id="advanced-search-icon" class="ias-icon-button" ng-click="$ctrl.toggleAdvancedSearch()"
+                ng-if="!$ctrl.advancedSearch && $ctrl.advancedSearchEnabled"
+                ng-attr-title="{{ 'Title_AdvancedSearch' | translate }}">
+        <ias-icon class="ias-selected" icon="search_advanced"></ias-icon>
+    </ias-button>
+    <ias-button id="close-advanced-search-icon" class="ias-icon-button" ng-click="$ctrl.toggleAdvancedSearch()"
+                ng-if="$ctrl.advancedSearch" ng-attr-title="{{ 'Button_Close' | translate }}">
+        <ias-icon class="ias-selected" icon="close_thin"></ias-icon>
+    </ias-button>
     <ias-button class="verifications-button ias-cta" ng-if="$ctrl.verificationsEnabled"
                 ng-click="$ctrl.showVerifications()">{{ 'Button_Verifications' | translate }}</ias-button>
 
@@ -54,6 +66,28 @@
     </ias-menu>
 </div>
 
+<div class="advanced-search-container" ng-if="$ctrl.advancedSearch">
+    <div class="attribute-row" ng-repeat="query in $ctrl.queries">
+        <select ng-model="query.key">
+            <option value="" selected disabled>{{ 'Display_SelectAttribute' | translate }}</option>
+            <option ng-repeat="tag in $ctrl.advancedSearchTags" ng-attr-value="{{tag.attribute}}">{{tag.label}}</option>
+        </select>
+        <input ng-model="query.value" autocomplete="off">
+        <ias-button class="ias-icon-button" ng-click="$ctrl.removeSearchTag($index)" ng-if="$index > 0"
+                    ng-attr-title="{{ 'Button_Remove' | translate }}">
+            <ias-icon icon="close_thin"></ias-icon>
+        </ias-button>
+    </div>
+    <ias-button id="add-attribute-row" class="ias-icon-button" ng-click="$ctrl.addSearchTag()"
+                ng-if="$ctrl.queries.length < $ctrl.advancedSearchMaxRows"
+                ng-attr-title="{{ 'Button_AddSearchAttribute' | translate }}">
+        <ias-icon icon="new_thin"></ias-icon>
+    </ias-button>
+    <ias-button id="advanced-search-button" ng-click="$ctrl.initiateSearch()"
+                ng-bind="'Placeholder_Search' | translate">
+    </ias-button>
+</div>
+
 <div class="search-info-container">
     <div class="search-info" ng-class="{'loading': !$ctrl.getMessage()}"
          ng-if="$ctrl.loading || $ctrl.searchMessage || $ctrl.errorMessage"

+ 14 - 0
client/src/modules/helpdesk/helpdesk-search.component.scss

@@ -43,6 +43,20 @@ help-desk-search-table {
     flex: 1 1;
     overflow: auto;
   }
+
+  .advanced-search-container {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+
+    > * + * {
+      margin-top: 5px;
+    }
+
+    &+ div {
+      margin-top: 15px;
+    }
+  }
 }
 
 .aligned-input {

+ 3 - 2
client/src/modules/peoplesearch/peoplesearch-base.component.ts

@@ -22,13 +22,14 @@
 
 import * as angular from 'angular';
 import {isArray, isString, IPromise, IQService, IScope, ITimeoutService} from 'angular';
-import {AdvancedSearchConfig, IPeopleSearchConfigService} from '../../services/peoplesearch-config.service';
+import { IPeopleSearchConfigService } from '../../services/peoplesearch-config.service';
 import { IPeopleService } from '../../services/people.service';
 import IPwmService from '../../services/pwm.service';
 import LocalStorageService from '../../services/local-storage.service';
 import { IPerson } from '../../models/person.model';
 import PromiseService from '../../services/promise.service';
 import SearchResult from '../../models/search-result.model';
+import {IAdvancedSearchConfig} from '../../services/base-config.service';
 
 abstract class PeopleSearchBaseComponent {
     advancedSearch = false;
@@ -229,7 +230,7 @@ abstract class PeopleSearchBaseComponent {
             this.orgChartEnabled = orgChartEnabled;
         });
 
-        this.configService.advancedSearchConfig().then((advancedSearchConfig: AdvancedSearchConfig) => {
+        this.configService.advancedSearchConfig().then((advancedSearchConfig: IAdvancedSearchConfig) => {
             this.advancedSearchEnabled = advancedSearchConfig.enabled;
             this.advancedSearchTags = advancedSearchConfig.attributes;
             this.advancedSearchMaxRows = advancedSearchConfig.maxRows;

+ 15 - 0
client/src/services/base-config.service.ts

@@ -26,12 +26,27 @@ import {IPwmService} from './pwm.service';
 const COLUMN_CONFIG = 'searchColumns';
 const PHOTO_ENABLED = 'enablePhoto';
 
+export const ADVANCED_SEARCH_ENABLED = 'enableAdvancedSearch';
+export const ADVANCED_SEARCH_MAX_ATTRIBUTES = 'maxAdvancedSearchAttributes';
+export const ADVANCED_SEARCH_ATTRIBUTES = 'advancedSearchAttributes';
+
 export interface IConfigService {
     getColumnConfig(): IPromise<any>;
     getValue(key: string): IPromise<any>;
     photosEnabled(): IPromise<boolean>;
 }
 
+export interface IAdvancedSearchConfig {
+    enabled: boolean;
+    maxRows: number;
+    attributes: {
+        attribute: string,
+        label: string,
+        type: string,
+        options: any
+    }[];
+}
+
 export abstract class ConfigBaseService implements IConfigService {
 
     constructor(protected $http: IHttpService,

+ 22 - 1
client/src/services/helpdesk-config.service.ts

@@ -24,7 +24,13 @@
 import { IHttpService, ILogService, IPromise, IQService } from 'angular';
 import IPwmService from './pwm.service';
 import PwmService from './pwm.service';
-import {ConfigBaseService, IConfigService} from './base-config.service';
+import {ConfigBaseService,
+    IAdvancedSearchConfig,
+    IConfigService,
+    ADVANCED_SEARCH_ENABLED,
+    ADVANCED_SEARCH_MAX_ATTRIBUTES,
+    ADVANCED_SEARCH_ATTRIBUTES
+} from './base-config.service';
 
 const CLEAR_RESPONSES_CONFIG = 'clearResponses';
 const CUSTOM_BUTTON_CONFIG = 'actions';
@@ -81,6 +87,7 @@ export interface IHelpDeskConfigService extends IConfigService {
     getVerificationMethods(options?: {includeOptional: boolean}): IPromise<IVerificationMap>;
     maskPasswordsEnabled(): IPromise<boolean>;
     verificationsEnabled(): IPromise<boolean>;
+    advancedSearchConfig(): IPromise<IAdvancedSearchConfig>;
 }
 
 export default class HelpDeskConfigService extends ConfigBaseService implements IConfigService, IHelpDeskConfigService {
@@ -164,4 +171,18 @@ export default class HelpDeskConfigService extends ConfigBaseService implements
                 return !!result.required.length;
             });
     }
+
+    advancedSearchConfig(): IPromise<IAdvancedSearchConfig> {
+        return this.$q.all([
+            this.getValue(ADVANCED_SEARCH_ENABLED),
+            this.getValue(ADVANCED_SEARCH_MAX_ATTRIBUTES),
+            this.getValue(ADVANCED_SEARCH_ATTRIBUTES)
+        ]).then((result) => {
+            return {
+                enabled: result[0],
+                maxRows: result[1],
+                attributes: result[2]
+            };
+        });
+    }
 }

+ 25 - 0
client/src/services/helpdesk.service.ts

@@ -26,6 +26,7 @@ import {ILogService, IPromise, IQService, IWindowService} from 'angular';
 import LocalStorageService from './local-storage.service';
 import ObjectService from './object.service';
 import SearchResult from '../models/search-result.model';
+import {IQuery} from './people.service';
 
 const VERIFICATION_PROCESS_ACTIONS = {
     ATTRIBUTES: 'validateAttributes',
@@ -47,6 +48,7 @@ export interface IHelpDeskService {
     getRandomPassword(userKey: string): IPromise<IRandomPasswordResponse>;
     getRecentVerifications(): IPromise<IRecentVerifications>;
     search(query: string): IPromise<SearchResult>;
+    advancedSearch(queries: IQuery[]): IPromise<SearchResult>;
     sendVerificationToken(userKey: string, choice: string): IPromise<IVerificationTokenResponse>;
     setPassword(userKey: string, random: boolean, password?: string): IPromise<ISuccessResponse>;
     unlockIntruder(userKey: string): IPromise<ISuccessResponse>;
@@ -229,6 +231,7 @@ export default class HelpDeskService implements IHelpDeskService {
             + '&pwmFormID=' + this.PWM_GLOBAL['pwmFormID'];
 
         let data = {
+            mode: 'simple',
             username: query,
             pwmFormID: formID
         };
@@ -244,6 +247,28 @@ export default class HelpDeskService implements IHelpDeskService {
             });
     }
 
+    advancedSearch(queries: IQuery[]): IPromise<SearchResult> {
+        let formID: string = encodeURIComponent('&pwmFormID=' + this.PWM_GLOBAL['pwmFormID']);
+        let url: string = this.pwmService.getServerUrl('search')
+            + '&pwmFormID=' + this.PWM_GLOBAL['pwmFormID'];
+
+        let data = {
+            mode: 'advanced',
+            pwmFormID: formID,
+            searchValues: queries
+        };
+        return this.pwmService
+            .httpRequest(url, {
+                data: data,
+                preventCache: true
+            })
+            .then((result: any) => {
+                let receivedData: any = result.data;
+                let searchResult: SearchResult = new SearchResult(receivedData);
+                return searchResult;
+            });
+    }
+
     sendVerificationToken(userKey: string, choice: string): IPromise<IVerificationTokenResponse> {
         let url: string = this.pwmService.getServerUrl('sendVerificationToken');
         let data: any = { userKey: userKey };

+ 10 - 17
client/src/services/peoplesearch-config.service.ts

@@ -24,31 +24,24 @@
 import { IHttpService, ILogService, IPromise, IQService } from 'angular';
 import IPwmService from './pwm.service';
 import PwmService from './pwm.service';
-import {ConfigBaseService, IConfigService} from './base-config.service';
+import {
+    ConfigBaseService,
+    IConfigService,
+    IAdvancedSearchConfig,
+    ADVANCED_SEARCH_ENABLED,
+    ADVANCED_SEARCH_MAX_ATTRIBUTES,
+    ADVANCED_SEARCH_ATTRIBUTES
+} from './base-config.service';
 
 const ORGCHART_ENABLED = 'orgChartEnabled';
 const ORGCHART_MAX_PARENTS = 'orgChartMaxParents';
 const ORGCHART_SHOW_CHILD_COUNT = 'orgChartShowChildCount';
-const ADVANCED_SEARCH_ENABLED = 'enableAdvancedSearch';
-const ADVANCED_SEARCH_MAX_ATTRIBUTES = 'maxAdvancedSearchAttributes';
-const ADVANCED_SEARCH_ATTRIBUTES = 'advancedSearchAttributes';
-
-export interface AdvancedSearchConfig {
-    enabled: boolean;
-    maxRows: number;
-    attributes: {
-        attribute: string,
-        label: string,
-        type: string,
-        options: any
-    }[];
-}
 
 export interface IPeopleSearchConfigService extends IConfigService {
     getOrgChartMaxParents(): IPromise<number>;
     orgChartEnabled(): IPromise<boolean>;
     orgChartShowChildCount(): IPromise<boolean>;
-    advancedSearchConfig(): IPromise<AdvancedSearchConfig>;
+    advancedSearchConfig(): IPromise<IAdvancedSearchConfig>;
 }
 
 export default class PeopleSearchConfigService
@@ -73,7 +66,7 @@ export default class PeopleSearchConfigService
         return this.getValue(ORGCHART_SHOW_CHILD_COUNT);
     }
 
-    advancedSearchConfig(): IPromise<AdvancedSearchConfig> {
+    advancedSearchConfig(): IPromise<IAdvancedSearchConfig> {
         return this.$q.all([
             this.getValue(ADVANCED_SEARCH_ENABLED),
             this.getValue(ADVANCED_SEARCH_MAX_ATTRIBUTES),