Преглед изворни кода

Implemented advanced search handling of attributes with a canned set of values

jalbr74 пре 6 година
родитељ
комит
edf4ef560d

+ 44 - 10
client/src/modules/helpdesk/helpdesk-search-base.component.ts

@@ -29,14 +29,15 @@ 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';
+import {IAdvancedSearchConfig, IAdvancedSearchQuery, IAttributeMetadata} 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: any[];
+    advancedSearchTags = {};
     advancedSearchEnabled: boolean;
     advancedSearchMaxRows: number;
     columnConfiguration: any;
@@ -45,8 +46,7 @@ export default abstract class HelpDeskSearchBaseComponent {
     protected pendingRequests: IPromise<any>[] = [];
     photosEnabled: boolean;
     query: string;
-    queries: any[];
-    initialQueryKey: string;
+    queries: IAdvancedSearchQuery[];
     searchMessage: string;
     searchResult: SearchResult;
     searchTextLocalStorageKey: string;
@@ -84,12 +84,10 @@ export default abstract class HelpDeskSearchBaseComponent {
 
         this.configService.advancedSearchConfig().then((advancedSearchConfig: IAdvancedSearchConfig) => {
             this.advancedSearchEnabled = advancedSearchConfig.enabled;
-            this.advancedSearchTags = advancedSearchConfig.attributes;
             this.advancedSearchMaxRows = advancedSearchConfig.maxRows;
 
-            // Save the first attribute to use as the initial selection of new query rows
-            if (this.advancedSearchTags && this.advancedSearchTags.length > 0) {
-                this.initialQueryKey = this.advancedSearchTags[0].attribute;
+            for (let advancedSearchTag of advancedSearchConfig.attributes) {
+                this.advancedSearchTags[advancedSearchTag.attribute] = advancedSearchTag;
             }
         });
 
@@ -120,7 +118,7 @@ export default abstract class HelpDeskSearchBaseComponent {
 
     protected clearSearch(): void {
         this.query = null;
-        this.queries = [{key: this.initialQueryKey, value: ''}];
+        this.queries = [];
         this.searchResult = null;
         this.clearErrorMessage();
         this.clearSearchMessage();
@@ -246,6 +244,20 @@ export default abstract class HelpDeskSearchBaseComponent {
         }
     }
 
+    private onAdvancedSearchAttributeChanged(query: IAdvancedSearchQuery) {
+        // Make sure we set the default value if the type is select
+        const attributeMetadata: IAttributeMetadata = this.advancedSearchTags[query.key];
+        if (attributeMetadata.type == 'select') {
+            query.value = this.getDefaultValue(attributeMetadata);
+        }
+
+        this.initiateSearch();
+    }
+
+    private onAdvancedSearchAttributeValueChanged() {
+        this.initiateSearch();
+    }
+
     private onAdvancedSearchValueChanged() {
         this.initiateSearch();
     }
@@ -263,7 +275,28 @@ export default abstract class HelpDeskSearchBaseComponent {
     }
 
     addSearchTag(): void {
-        this.queries.push({key: this.initialQueryKey, value: ''});
+        const firstTagName = Object.keys(this.advancedSearchTags)[0];
+        const attributeMetaData: IAttributeMetadata = this.advancedSearchTags[firstTagName];
+
+        const query: IAdvancedSearchQuery = {
+            key: attributeMetaData.attribute,
+            value: this.getDefaultValue(attributeMetaData),
+        };
+
+        this.queries.push(query);
+    }
+
+    private getDefaultValue(attributeMetaData: IAttributeMetadata) {
+        if (attributeMetaData) {
+            if (attributeMetaData.type === 'select') {
+                const keys: string[] = Object.keys(attributeMetaData.options);
+                if (keys && keys.length > 0) {
+                    return keys[0];
+                }
+            }
+        }
+
+        return '';
     }
 
     protected selectPerson(person: IPerson): void {
@@ -292,6 +325,7 @@ export default abstract class HelpDeskSearchBaseComponent {
 
     enableAdvancedSearch(): void {
         this.clearSearch();
+        this.addSearchTag();
         this.advancedSearch = true;
     }
 

+ 14 - 3
client/src/modules/helpdesk/helpdesk-search-cards.component.html

@@ -37,11 +37,22 @@
     </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" ng-change="$ctrl.onAdvancedSearchValueChanged($event)">
+            <select ng-model="query.key" ng-change="$ctrl.onAdvancedSearchAttributeChanged(query)">
                 <option ng-repeat="tag in $ctrl.advancedSearchTags" ng-attr-value="{{tag.attribute}}">{{tag.label}}</option>
             </select>
-            <input ng-model="query.value" autocomplete="off" ng-change="$ctrl.onAdvancedSearchValueChanged($event)"
-                   ng-model-options="{debounce: $ctrl.inputDebounce}">
+
+            <!--Show a drop-down if the attribute type is 'select'-->
+            <select ng-model="query.value" ng-if="$ctrl.advancedSearchTags[query.key].type === 'select'"
+                    ng-change="$ctrl.onAdvancedSearchValueChanged($event)" class="attribute-value">
+                <option ng-attr-value="{{name}}"
+                        ng-repeat="(name, label) in $ctrl.advancedSearchTags[query.key].options">{{label}}</option>
+            </select>
+
+            <!--Otherwise, just show a regular input field-->
+            <input ng-model="query.value" ng-if="$ctrl.advancedSearchTags[query.key].type !== 'select'"
+                   ng-change="$ctrl.onAdvancedSearchValueChanged($event)" class="attribute-value"
+                   autocomplete="off" ng-model-options="{debounce: $ctrl.inputDebounce}">
+
             <ias-button class="ias-icon-button" ng-click="$ctrl.removeSearchTag($index)"
                         ng-attr-title="{{ 'Button_Remove' | translate }}">
                 <ias-icon icon="close_thin"></ias-icon>

+ 14 - 3
client/src/modules/helpdesk/helpdesk-search-table.component.html

@@ -36,11 +36,22 @@
     </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" ng-change="$ctrl.onAdvancedSearchValueChanged($event)">
+            <select ng-model="query.key" ng-change="$ctrl.onAdvancedSearchAttributeChanged(query)">
                 <option ng-repeat="tag in $ctrl.advancedSearchTags" ng-attr-value="{{tag.attribute}}">{{tag.label}}</option>
             </select>
-            <input ng-model="query.value" autocomplete="off" ng-change="$ctrl.onAdvancedSearchValueChanged($event)"
-                   ng-model-options="{debounce: $ctrl.inputDebounce}">
+
+            <!--Show a drop-down if the attribute type is 'select'-->
+            <select ng-model="query.value" ng-if="$ctrl.advancedSearchTags[query.key].type === 'select'"
+                    ng-change="$ctrl.onAdvancedSearchValueChanged($event)" class="attribute-value">
+                <option ng-attr-value="{{name}}"
+                        ng-repeat="(name, label) in $ctrl.advancedSearchTags[query.key].options">{{label}}</option>
+            </select>
+
+            <!--Otherwise, just show a regular input field-->
+            <input ng-model="query.value" ng-if="$ctrl.advancedSearchTags[query.key].type !== 'select'"
+                   ng-change="$ctrl.onAdvancedSearchValueChanged($event)" class="attribute-value"
+                   autocomplete="off" ng-model-options="{debounce: $ctrl.inputDebounce}">
+
             <ias-button class="ias-icon-button" ng-click="$ctrl.removeSearchTag($index)"
                         ng-attr-title="{{ 'Button_Remove' | translate }}">
                 <ias-icon icon="close_thin"></ias-icon>

+ 43 - 10
client/src/modules/peoplesearch/peoplesearch-base.component.ts

@@ -29,11 +29,11 @@ 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';
+import {IAdvancedSearchConfig, IAdvancedSearchQuery, IAttributeMetadata} from '../../services/base-config.service';
 
 abstract class PeopleSearchBaseComponent {
     advancedSearch = false;
-    advancedSearchTags: any[];
+    advancedSearchTags = {};
     advancedSearchEnabled: boolean;
     advancedSearchMaxRows: number;
     errorMessage: string;
@@ -43,8 +43,7 @@ abstract class PeopleSearchBaseComponent {
     searchMessage: string;
     searchResult: SearchResult;
     query: string;
-    queries: any[];
-    initialQueryKey: string;
+    queries: IAdvancedSearchQuery[];
     searchTextLocalStorageKey: string;
     searchViewLocalStorageKey: string;
 
@@ -87,6 +86,20 @@ abstract class PeopleSearchBaseComponent {
         this.fetchData();
     }
 
+    private onAdvancedSearchAttributeChanged(query: IAdvancedSearchQuery) {
+        // Make sure we set the default value if the type is select
+        const attributeMetadata: IAttributeMetadata = this.advancedSearchTags[query.key];
+        if (attributeMetadata.type == 'select') {
+            query.value = this.getDefaultValue(attributeMetadata);
+        }
+
+        this.initiateSearch();
+    }
+
+    private onAdvancedSearchAttributeValueChanged() {
+        this.initiateSearch();
+    }
+
     private onAdvancedSearchValueChanged() {
         this.initiateSearch();
     }
@@ -113,7 +126,28 @@ abstract class PeopleSearchBaseComponent {
     }
 
     addSearchTag(): void {
-        this.queries.push({key: this.initialQueryKey, value: ''});
+        const firstTagName = Object.keys(this.advancedSearchTags)[0];
+        const attributeMetaData: IAttributeMetadata = this.advancedSearchTags[firstTagName];
+
+        const query: IAdvancedSearchQuery = {
+            key: attributeMetaData.attribute,
+            value: this.getDefaultValue(attributeMetaData),
+        };
+
+        this.queries.push(query);
+    }
+
+    private getDefaultValue(attributeMetaData: IAttributeMetadata) {
+        if (attributeMetaData) {
+            if (attributeMetaData.type === 'select') {
+                const keys: string[] = Object.keys(attributeMetaData.options);
+                if (keys && keys.length > 0) {
+                    return keys[0];
+                }
+            }
+        }
+
+        return '';
     }
 
     selectPerson(person: IPerson): void {
@@ -167,7 +201,7 @@ abstract class PeopleSearchBaseComponent {
 
     protected clearSearch(): void {
         this.query = null;
-        this.queries = [{key: this.initialQueryKey, value: ''}];
+        this.queries = [];
         this.searchResult = null;
         this.clearErrorMessage();
         this.clearSearchMessage();
@@ -246,12 +280,10 @@ abstract class PeopleSearchBaseComponent {
 
         this.configService.advancedSearchConfig().then((advancedSearchConfig: IAdvancedSearchConfig) => {
             this.advancedSearchEnabled = advancedSearchConfig.enabled;
-            this.advancedSearchTags = advancedSearchConfig.attributes;
             this.advancedSearchMaxRows = advancedSearchConfig.maxRows;
 
-            // Save the first attribute to use as the initial selection of new query rows
-            if (this.advancedSearchTags && this.advancedSearchTags.length > 0) {
-                this.initialQueryKey = this.advancedSearchTags[0].attribute;
+            for (let advancedSearchTag of advancedSearchConfig.attributes) {
+                this.advancedSearchTags[advancedSearchTag.attribute] = advancedSearchTag;
             }
         });
 
@@ -282,6 +314,7 @@ abstract class PeopleSearchBaseComponent {
 
     enableAdvancedSearch(): void {
         this.clearSearch();
+        this.addSearchTag();
         this.advancedSearch = true;
     }
 

+ 14 - 3
client/src/modules/peoplesearch/peoplesearch-cards.component.html

@@ -36,11 +36,22 @@
     </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" ng-change="$ctrl.onAdvancedSearchValueChanged($event)">
+            <select ng-model="query.key" ng-change="$ctrl.onAdvancedSearchAttributeChanged(query)">
                 <option ng-repeat="tag in $ctrl.advancedSearchTags" ng-attr-value="{{tag.attribute}}">{{tag.label}}</option>
             </select>
-            <input ng-model="query.value" autocomplete="off" ng-change="$ctrl.onAdvancedSearchValueChanged($event)"
-                   ng-model-options="{debounce: $ctrl.inputDebounce}">
+
+            <!--Show a drop-down if the attribute type is 'select'-->
+            <select ng-model="query.value" ng-if="$ctrl.advancedSearchTags[query.key].type === 'select'"
+                    ng-change="$ctrl.onAdvancedSearchValueChanged($event)" class="attribute-value">
+                <option ng-attr-value="{{name}}"
+                        ng-repeat="(name, label) in $ctrl.advancedSearchTags[query.key].options">{{label}}</option>
+            </select>
+
+            <!--Otherwise, just show a regular input field-->
+            <input ng-model="query.value" ng-if="$ctrl.advancedSearchTags[query.key].type !== 'select'"
+                   ng-change="$ctrl.onAdvancedSearchValueChanged($event)" class="attribute-value"
+                   autocomplete="off" ng-model-options="{debounce: $ctrl.inputDebounce}">
+
             <ias-button class="ias-icon-button" ng-click="$ctrl.removeSearchTag($index)"
                         ng-attr-title="{{ 'Button_Remove' | translate }}">
                 <ias-icon icon="close_thin"></ias-icon>

+ 14 - 3
client/src/modules/peoplesearch/peoplesearch-table.component.html

@@ -36,11 +36,22 @@
     </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" ng-change="$ctrl.onAdvancedSearchValueChanged($event)">
+            <select ng-model="query.key" ng-change="$ctrl.onAdvancedSearchAttributeChanged(query)">
                 <option ng-repeat="tag in $ctrl.advancedSearchTags" ng-attr-value="{{tag.attribute}}">{{tag.label}}</option>
             </select>
-            <input ng-model="query.value" autocomplete="off" ng-change="$ctrl.onAdvancedSearchValueChanged($event)"
-                   ng-model-options="{debounce: $ctrl.inputDebounce}">
+
+            <!--Show a drop-down if the attribute type is 'select'-->
+            <select ng-model="query.value" ng-if="$ctrl.advancedSearchTags[query.key].type === 'select'"
+                    ng-change="$ctrl.onAdvancedSearchValueChanged($event)" class="attribute-value">
+                <option ng-attr-value="{{name}}"
+                        ng-repeat="(name, label) in $ctrl.advancedSearchTags[query.key].options">{{label}}</option>
+            </select>
+
+            <!--Otherwise, just show a regular input field-->
+            <input ng-model="query.value" ng-if="$ctrl.advancedSearchTags[query.key].type !== 'select'"
+                   ng-change="$ctrl.onAdvancedSearchValueChanged($event)" class="attribute-value"
+                   autocomplete="off" ng-model-options="{debounce: $ctrl.inputDebounce}">
+
             <ias-button class="ias-icon-button" ng-click="$ctrl.removeSearchTag($index)"
                         ng-attr-title="{{ 'Button_Remove' | translate }}">
                 <ias-icon icon="close_thin"></ias-icon>

+ 4 - 0
client/src/modules/peoplesearch/peoplesearch.scss

@@ -74,6 +74,10 @@ body {
       &+ div {
         margin-top: 15px;
       }
+
+      select.attribute-value {
+        min-width: 210px;
+      }
     }
   }
 

+ 13 - 6
client/src/services/base-config.service.ts

@@ -36,15 +36,22 @@ export interface IConfigService {
     photosEnabled(): IPromise<boolean>;
 }
 
+export interface IAttributeMetadata {
+    attribute: string;
+    label: string;
+    type: string;
+    options: any;
+}
+
 export interface IAdvancedSearchConfig {
     enabled: boolean;
     maxRows: number;
-    attributes: {
-        attribute: string,
-        label: string,
-        type: string,
-        options: any
-    }[];
+    attributes: IAttributeMetadata[];
+}
+
+export interface IAdvancedSearchQuery {
+    key: string;
+    value: string;
 }
 
 export abstract class ConfigBaseService implements IConfigService {