helpdesk-search-base.component.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. /*
  2. * Password Management Servlets (PWM)
  3. * http://www.pwm-project.org
  4. *
  5. * Copyright (c) 2006-2009 Novell, Inc.
  6. * Copyright (c) 2009-2018 The PWM Project
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  21. */
  22. import SearchResult from '../../models/search-result.model';
  23. import {isArray, isString, IPromise, IQService, IScope, ITimeoutService} from 'angular';
  24. import {IPerson} from '../../models/person.model';
  25. import {IHelpDeskConfigService} from '../../services/helpdesk-config.service';
  26. import LocalStorageService from '../../services/local-storage.service';
  27. import PromiseService from '../../services/promise.service';
  28. import {IHelpDeskService} from '../../services/helpdesk.service';
  29. import IPwmService from '../../services/pwm.service';
  30. import {IAdvancedSearchConfig, IAdvancedSearchQuery, IAttributeMetadata} from '../../services/base-config.service';
  31. import CommonSearchService from '../../services/common-search.service';
  32. let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html');
  33. let recentVerificationsDialogTemplateUrl = require('./recent-verifications-dialog.template.html');
  34. export default abstract class HelpDeskSearchBaseComponent {
  35. advancedSearch = false;
  36. advancedSearchTags = {};
  37. advancedSearchEnabled: boolean;
  38. advancedSearchMaxRows: number;
  39. columnConfiguration: any;
  40. errorMessage: string;
  41. inputDebounce: number;
  42. protected pendingRequests: IPromise<any>[] = [];
  43. photosEnabled: boolean;
  44. query: string;
  45. queries: IAdvancedSearchQuery[];
  46. searchMessage: string;
  47. searchResult: SearchResult;
  48. searchTextLocalStorageKey: string;
  49. searchViewLocalStorageKey: string;
  50. verificationsEnabled: boolean;
  51. constructor(protected $q: IQService,
  52. protected $scope: IScope,
  53. protected $state: angular.ui.IStateService,
  54. protected $stateParams: angular.ui.IStateParamsService,
  55. protected $timeout: ITimeoutService,
  56. protected $translate: angular.translate.ITranslateService,
  57. protected configService: IHelpDeskConfigService,
  58. protected helpDeskService: IHelpDeskService,
  59. protected IasDialogService: any,
  60. protected localStorageService: LocalStorageService,
  61. protected promiseService: PromiseService,
  62. protected pwmService: IPwmService,
  63. protected commonSearchService: CommonSearchService) {
  64. this.searchTextLocalStorageKey = this.localStorageService.keys.HELPDESK_SEARCH_TEXT;
  65. this.searchViewLocalStorageKey = this.localStorageService.keys.HELPDESK_SEARCH_VIEW;
  66. this.inputDebounce = this.pwmService.ajaxTypingWait;
  67. }
  68. protected initialize(): IPromise<void> {
  69. return this.$q.all(
  70. [
  71. this.configService.verificationsEnabled().then((verificationsEnabled: boolean) => {
  72. this.verificationsEnabled = verificationsEnabled;
  73. }),
  74. this.configService.advancedSearchConfig().then((advancedSearchConfig: IAdvancedSearchConfig) => {
  75. this.advancedSearchEnabled = advancedSearchConfig.enabled;
  76. this.advancedSearchMaxRows = advancedSearchConfig.maxRows;
  77. for (let advancedSearchTag of advancedSearchConfig.attributes) {
  78. this.advancedSearchTags[advancedSearchTag.attribute] = advancedSearchTag;
  79. }
  80. })
  81. ]
  82. ).then(result => {
  83. const searchQuery = this.getSearchQuery();
  84. if (searchQuery) {
  85. // A search query has been passed in, disregard the current search state
  86. this.query = searchQuery;
  87. this.advancedSearch = false;
  88. this.storeSearchText();
  89. this.commonSearchService.setHdAdvancedSearchActive(this.advancedSearch);
  90. this.commonSearchService.setHdAdvSearchQueries([]);
  91. } else {
  92. this.query = this.getSearchText();
  93. this.advancedSearch = this.commonSearchService.isHdAdvancedSearchActive();
  94. this.queries = this.commonSearchService.getHdAdvSearchQueries();
  95. if (this.queries.length === 0) {
  96. this.addSearchTag();
  97. }
  98. }
  99. // Once <ias-search-box> from ng-ias allows the autofocus attribute, we can remove this code
  100. this.$timeout(() => {
  101. document.getElementsByTagName('input')[0].focus();
  102. });
  103. this.$scope.$watch('$ctrl.query', (newValue: string, oldValue: string) => {
  104. this.onSearchTextChange(newValue, oldValue);
  105. });
  106. });
  107. }
  108. getMessage(): string {
  109. return this.errorMessage || this.searchMessage;
  110. }
  111. private getSearchQuery(): string {
  112. let param: string = this.$stateParams['query'];
  113. // If multiple query parameters are defined, use the first one
  114. if (isArray(param)) {
  115. param = param[0].trim();
  116. }
  117. else if (isString(param)) {
  118. param = param.trim();
  119. }
  120. return param;
  121. }
  122. private getSearchText(): string {
  123. return this.localStorageService.getItem(this.searchTextLocalStorageKey);
  124. }
  125. abstract fetchData(): void;
  126. protected clearSearch(): void {
  127. this.query = null;
  128. this.queries = [];
  129. this.searchResult = null;
  130. this.clearErrorMessage();
  131. this.clearSearchMessage();
  132. this.abortPendingRequests();
  133. }
  134. protected fetchSearchData(): IPromise<void | SearchResult> {
  135. this.abortPendingRequests();
  136. this.searchResult = null;
  137. let promise;
  138. if (this.advancedSearch) {
  139. if (!this.queries || (this.queries.length === 1 && !this.queries[0].key)) {
  140. this.clearSearch();
  141. return null;
  142. }
  143. const keys = new Set();
  144. for (let searchQuery of this.queries) {
  145. keys.add(searchQuery.key);
  146. }
  147. const duplicateSearchAttrsFound = keys.size < this.queries.length;
  148. if (duplicateSearchAttrsFound) {
  149. this.$translate('Display_SearchAttrsUnique')
  150. .then((translation: string) => {
  151. this.searchMessage = translation;
  152. });
  153. return null;
  154. }
  155. promise = this.helpDeskService.advancedSearch(this.queries);
  156. }
  157. else {
  158. if (!this.query) {
  159. this.clearSearch();
  160. return null;
  161. }
  162. promise = this.helpDeskService.search(this.query);
  163. }
  164. this.pendingRequests.push(promise);
  165. return promise
  166. .then(
  167. function(searchResult: SearchResult) {
  168. this.clearErrorMessage();
  169. this.clearSearchMessage();
  170. // Aborted request
  171. if (!searchResult) {
  172. return;
  173. }
  174. // Too many results returned
  175. if (searchResult.sizeExceeded) {
  176. this.setSearchMessage('Display_SearchResultsExceeded');
  177. }
  178. // No results returned. Not an else if statement so that the more important message is presented
  179. if (!searchResult.people.length) {
  180. this.setSearchMessage('Display_SearchResultsNone');
  181. }
  182. return searchResult;
  183. }.bind(this),
  184. function(error) {
  185. this.setErrorMessage(error);
  186. this.clearSearchMessage();
  187. }.bind(this))
  188. .finally(function() {
  189. this.removePendingRequest(promise);
  190. }.bind(this));
  191. }
  192. private gotoState(state: string): void {
  193. this.$state.go(state);
  194. }
  195. private initiateSearch() {
  196. this.clearSearchMessage();
  197. this.clearErrorMessage();
  198. this.fetchData();
  199. }
  200. private onSearchTextChange(newValue: string, oldValue: string): void {
  201. if (newValue === oldValue) {
  202. return;
  203. }
  204. this.storeSearchText();
  205. this.initiateSearch();
  206. }
  207. protected abortPendingRequests() {
  208. for (let index = 0; index < this.pendingRequests.length; index++) {
  209. let pendingRequest = this.pendingRequests[index];
  210. this.promiseService.abort(pendingRequest);
  211. }
  212. this.pendingRequests = [];
  213. }
  214. protected setErrorMessage(message: string) {
  215. this.errorMessage = message;
  216. }
  217. protected clearErrorMessage() {
  218. this.errorMessage = null;
  219. }
  220. // If message is a string it will be translated. If it is a promise it will assign the string from the resolved
  221. // promise
  222. protected setSearchMessage(translationKey: string) {
  223. if (!translationKey) {
  224. this.clearSearchMessage();
  225. return;
  226. }
  227. const self = this;
  228. this.$translate(translationKey.toString())
  229. .then((translation: string) => {
  230. self.searchMessage = translation;
  231. });
  232. }
  233. protected clearSearchMessage(): void {
  234. this.searchMessage = null;
  235. }
  236. protected removePendingRequest(promise: IPromise<any>) {
  237. let index = this.pendingRequests.indexOf(promise);
  238. if (index > -1) {
  239. this.pendingRequests.splice(index, 1);
  240. }
  241. }
  242. private onAdvancedSearchAttributeChanged(query: IAdvancedSearchQuery) {
  243. // Make sure we set the default value if the type is select
  244. const attributeMetadata: IAttributeMetadata = this.advancedSearchTags[query.key];
  245. if (attributeMetadata.type == 'select') {
  246. query.value = this.commonSearchService.getDefaultValue(attributeMetadata);
  247. }
  248. this.commonSearchService.setHdAdvSearchQueries(this.queries);
  249. this.initiateSearch();
  250. }
  251. private onAdvancedSearchAttributeValueChanged() {
  252. this.commonSearchService.setHdAdvSearchQueries(this.queries);
  253. this.initiateSearch();
  254. }
  255. private onAdvancedSearchValueChanged() {
  256. this.commonSearchService.setHdAdvSearchQueries(this.queries);
  257. this.initiateSearch();
  258. }
  259. removeSearchTag(tagIndex: number): void {
  260. this.queries.splice(tagIndex, 1);
  261. this.commonSearchService.setHdAdvSearchQueries(this.queries);
  262. if (this.queries.length > 0) {
  263. this.initiateSearch();
  264. }
  265. else {
  266. this.clearSearch();
  267. this.advancedSearch = false;
  268. this.commonSearchService.setHdAdvancedSearchActive(this.advancedSearch);
  269. }
  270. }
  271. addSearchTag(): void {
  272. const firstTagName = Object.keys(this.advancedSearchTags)[0];
  273. const attributeMetaData: IAttributeMetadata = this.advancedSearchTags[firstTagName];
  274. const query: IAdvancedSearchQuery = {
  275. key: attributeMetaData.attribute,
  276. value: this.commonSearchService.getDefaultValue(attributeMetaData),
  277. };
  278. this.queries.push(query);
  279. }
  280. protected selectPerson(person: IPerson): void {
  281. this.IasDialogService
  282. .open({
  283. controller: 'VerificationsDialogController as $ctrl',
  284. templateUrl: verificationsDialogTemplateUrl,
  285. locals: {
  286. personUserKey: person.userKey,
  287. showRequiredOnly: true
  288. }
  289. });
  290. }
  291. protected showVerifications(): void {
  292. this.IasDialogService
  293. .open({
  294. controller: 'RecentVerificationsDialogController as $ctrl',
  295. templateUrl: recentVerificationsDialogTemplateUrl
  296. });
  297. }
  298. protected storeSearchText(): void {
  299. this.localStorageService.setItem(this.searchTextLocalStorageKey, this.query || '');
  300. }
  301. enableAdvancedSearch(): void {
  302. this.clearSearch();
  303. this.addSearchTag();
  304. this.advancedSearch = true;
  305. this.commonSearchService.setHdAdvancedSearchActive(this.advancedSearch);
  306. }
  307. protected toggleView(state: string): void {
  308. this.storeSearchView(state);
  309. this.storeSearchText();
  310. this.gotoState(state);
  311. }
  312. private storeSearchView(state: string) {
  313. this.localStorageService.setItem(this.searchViewLocalStorageKey, state);
  314. }
  315. }