1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import { unionize, ofType, UnionOf } from "~/common/unionize";
6 import { GroupContentsResource, GroupContentsResourcePrefix } from '~/services/groups-service/groups-service';
7 import { Dispatch } from 'redux';
8 import { change, arrayPush } from 'redux-form';
9 import { RootState } from '~/store/store';
10 import { initUserProject } from '~/store/tree-picker/tree-picker-actions';
11 import { ServiceRepository } from '~/services/services';
12 import { FilterBuilder } from "~/services/api/filter-builder";
13 import { ResourceKind } from '~/models/resource';
14 import { GroupClass } from '~/models/group';
15 import { SearchView } from '~/store/search-bar/search-bar-reducer';
16 import { navigateToSearchResults, navigateTo } from '~/store/navigation/navigation-action';
17 import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
18 import { initialize } from 'redux-form';
19 import { SearchBarAdvanceFormData, PropertyValues } from '~/models/search-bar';
20 import { debounce } from 'debounce';
22 export const searchBarActions = unionize({
23 SET_CURRENT_VIEW: ofType<string>(),
24 OPEN_SEARCH_VIEW: ofType<{}>(),
25 CLOSE_SEARCH_VIEW: ofType<{}>(),
26 SET_SEARCH_RESULTS: ofType<GroupContentsResource[]>(),
27 SET_SEARCH_VALUE: ofType<string>(),
28 SET_SAVED_QUERIES: ofType<SearchBarAdvanceFormData[]>(),
29 UPDATE_SAVED_QUERY: ofType<SearchBarAdvanceFormData[]>()
32 export type SearchBarActions = UnionOf<typeof searchBarActions>;
34 export const SEARCH_BAR_ADVANCE_FORM_NAME = 'searchBarAdvanceFormName';
36 export const SEARCH_BAR_ADVANCE_FORM_PICKER_ID = 'searchBarAdvanceFormPickerId';
38 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
40 export const goToView = (currentView: string) => searchBarActions.SET_CURRENT_VIEW(currentView);
42 export const saveRecentQuery = (query: string) =>
43 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) =>
44 services.searchService.saveRecentQuery(query);
47 export const loadRecentQueries = () =>
48 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
49 const recentSearchQueries = services.searchService.getRecentQueries();
50 return recentSearchQueries || [];
53 export const searchData = (searchValue: string) =>
54 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
55 const currentView = getState().searchBar.currentView;
56 dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
57 dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
58 dispatch<any>(searchGroups(searchValue));
59 if (currentView === SearchView.BASIC) {
60 dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
61 dispatch(navigateToSearchResults);
66 export const searchAdvanceData = (data: SearchBarAdvanceFormData) =>
67 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
68 const searchValue = getState().searchBar.searchValue;
69 dispatch<any>(saveQuery(data));
70 dispatch<any>(searchGroups(searchValue, 100, data.type));
71 dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC));
72 dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
73 dispatch(navigateToSearchResults);
76 // Todo: create ids for particular searchQuery
77 const saveQuery = (data: SearchBarAdvanceFormData) =>
78 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
79 const savedSearchQueries = services.searchService.getSavedQueries();
80 const filteredQuery = savedSearchQueries.find(query => query.searchQuery === data.searchQuery);
81 if (data.saveQuery && data.searchQuery) {
83 services.searchService.editSavedQueries(data);
84 dispatch(searchBarActions.UPDATE_SAVED_QUERY(savedSearchQueries));
85 dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Query has been sucessfully updated', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
87 services.searchService.saveQuery(data);
88 dispatch(searchBarActions.SET_SAVED_QUERIES(savedSearchQueries));
89 dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Query has been sucessfully saved', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
94 export const deleteSavedQuery = (id: number) =>
95 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
96 services.searchService.deleteSavedQuery(id);
97 const savedSearchQueries = services.searchService.getSavedQueries();
98 dispatch(searchBarActions.SET_SAVED_QUERIES(savedSearchQueries));
99 return savedSearchQueries || [];
102 export const editSavedQuery = (data: SearchBarAdvanceFormData) =>
103 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
104 dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.ADVANCED));
105 dispatch(searchBarActions.SET_SEARCH_VALUE(data.searchQuery));
106 dispatch<any>(initialize(SEARCH_BAR_ADVANCE_FORM_NAME, data));
109 export const openSearchView = () =>
110 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
111 dispatch(searchBarActions.OPEN_SEARCH_VIEW());
112 const savedSearchQueries = services.searchService.getSavedQueries();
113 dispatch(searchBarActions.SET_SAVED_QUERIES(savedSearchQueries));
116 export const closeSearchView = () =>
117 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
118 const isOpen = getState().searchBar.open;
120 dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
121 dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC));
125 export const closeAdvanceView = () =>
126 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
127 dispatch(searchBarActions.SET_SEARCH_VALUE(''));
128 dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC));
131 export const navigateToItem = (uuid: string) =>
132 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
133 dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
134 dispatch(navigateTo(uuid));
137 export const changeData = (searchValue: string) =>
138 (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
139 dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
140 const currentView = getState().searchBar.currentView;
141 const searchValuePresent = searchValue.length > 0;
143 if (currentView === SearchView.ADVANCED) {
145 } else if (searchValuePresent) {
146 dispatch<any>(goToView(SearchView.AUTOCOMPLETE));
147 debounceStartSearch(dispatch);
149 dispatch<any>(goToView(SearchView.BASIC));
150 dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
151 dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
155 export const submitData = (event: React.FormEvent<HTMLFormElement>) =>
156 (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
157 event.preventDefault();
158 const searchValue = getState().searchBar.searchValue;
159 dispatch<any>(saveRecentQuery(searchValue));
160 dispatch<any>(searchDataOnEnter(searchValue));
161 dispatch<any>(loadRecentQueries());
164 const searchDataOnEnter = (searchValue: string) =>
165 (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
166 dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
167 dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
168 dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
169 dispatch<any>(searchGroups(searchValue, 100));
170 dispatch(navigateToSearchResults);
173 const debounceStartSearch = debounce((dispatch: Dispatch) => dispatch<any>(startSearch()), DEFAULT_SEARCH_DEBOUNCE);
175 const startSearch = () =>
176 (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
177 const searchValue = getState().searchBar.searchValue;
178 dispatch<any>(searchData(searchValue));
181 const searchGroups = (searchValue: string, limit = 5, resourceKind?: ResourceKind) =>
182 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
183 const currentView = getState().searchBar.currentView;
185 if (searchValue || currentView === SearchView.ADVANCED) {
186 const filters = getFilters('name', searchValue, resourceKind);
187 const { items } = await services.groupsService.contents('', {
192 dispatch(searchBarActions.SET_SEARCH_RESULTS(items));
196 export const getFilters = (filterName: string, searchValue: string, resourceKind?: ResourceKind): string => {
197 return new FilterBuilder()
198 .addIsA("uuid", buildUuidFilter(resourceKind))
199 .addILike(filterName, searchValue, GroupContentsResourcePrefix.COLLECTION)
200 .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROCESS)
201 .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROJECT)
202 .addEqual('groupClass', GroupClass.PROJECT, GroupContentsResourcePrefix.PROJECT)
206 const buildUuidFilter = (type?: ResourceKind): ResourceKind[] => {
207 return type ? [type] : [ResourceKind.PROJECT, ResourceKind.COLLECTION, ResourceKind.PROCESS];
210 export const initAdvanceFormProjectsTree = () =>
211 (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
212 dispatch<any>(initUserProject(SEARCH_BAR_ADVANCE_FORM_PICKER_ID));
215 export const changeAdvanceFormProperty = (property: string, value: PropertyValues[] | string = '') =>
216 (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
217 dispatch(change(SEARCH_BAR_ADVANCE_FORM_NAME, property, value));
220 export const updateAdvanceFormProperties = (propertyValues: PropertyValues) =>
221 (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
222 dispatch(arrayPush(SEARCH_BAR_ADVANCE_FORM_NAME, 'properties', propertyValues));