// SPDX-License-Identifier: AGPL-3.0
import { PropertyValue } from "~/models/search-bar";
+import { Vocabulary, getTagKeyLabel, getTagValueLabel } from "~/models/vocabulary";
export const formatDate = (isoDate?: string | null, utc: boolean = false) => {
if (isoDate) {
}
];
-export const formatPropertyValue = (pv: PropertyValue) => {
+export const formatPropertyValue = (pv: PropertyValue, vocabulary?: Vocabulary) => {
+ if (vocabulary && pv.keyID && pv.valueID) {
+ return `${getTagKeyLabel(pv.keyID, vocabulary)}: ${getTagValueLabel(pv.keyID, pv.valueID!, vocabulary)}`;
+ }
if (pv.key) {
return pv.value
? `${pv.key}: ${pv.value}`
import { getBuildInfo } from '~/common/app-info';
import { DragDropContextProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
-import { initAdvanceFormProjectsTree } from '~/store/search-bar/search-bar-actions';
+import { initAdvancedFormProjectsTree } from '~/store/search-bar/search-bar-actions';
import { repositoryActionSet } from '~/views-components/context-menu/action-sets/repository-action-set';
import { sshKeyActionSet } from '~/views-components/context-menu/action-sets/ssh-key-action-set';
import { keepServiceActionSet } from '~/views-components/context-menu/action-sets/keep-service-action-set';
await store.dispatch(loadWorkbench());
addRouteChangeHandlers(history, store);
// ToDo: move to searchBar component
- store.dispatch(initAdvanceFormProjectsTree());
+ store.dispatch(initAdvancedFormProjectsTree());
}
};
};
import { ResourceKind } from '~/models/resource';
-export type SearchBarAdvanceFormData = {
+export type SearchBarAdvancedFormData = {
type?: ResourceKind;
cluster?: string;
projectUuid?: string;
export interface PropertyValue {
key: string;
+ keyID?: string;
value: string;
+ valueID?: string;
}
//
// SPDX-License-Identifier: AGPL-3.0
-import { SearchBarAdvanceFormData } from '~/models/search-bar';
+import { SearchBarAdvancedFormData } from '~/models/search-bar';
export class SearchService {
private recentQueries = this.getRecentQueries();
- private savedQueries: SearchBarAdvanceFormData[] = this.getSavedQueries();
+ private savedQueries: SearchBarAdvancedFormData[] = this.getSavedQueries();
saveRecentQuery(query: string) {
if (this.recentQueries.length >= MAX_NUMBER_OF_RECENT_QUERIES) {
return JSON.parse(localStorage.getItem('recentQueries') || '[]');
}
- saveQuery(data: SearchBarAdvanceFormData) {
+ saveQuery(data: SearchBarAdvancedFormData) {
this.savedQueries.push({...data});
localStorage.setItem('savedQueries', JSON.stringify(this.savedQueries));
}
- editSavedQueries(data: SearchBarAdvanceFormData) {
+ editSavedQueries(data: SearchBarAdvancedFormData) {
const itemIndex = this.savedQueries.findIndex(item => item.queryName === data.queryName);
this.savedQueries[itemIndex] = {...data};
localStorage.setItem('savedQueries', JSON.stringify(this.savedQueries));
}
getSavedQueries() {
- return JSON.parse(localStorage.getItem('savedQueries') || '[]') as SearchBarAdvanceFormData[];
+ return JSON.parse(localStorage.getItem('savedQueries') || '[]') as SearchBarAdvancedFormData[];
}
deleteSavedQuery(id: number) {
import { ofType, unionize, UnionOf } from "~/common/unionize";
import { GroupContentsResource, GroupContentsResourcePrefix } from '~/services/groups-service/groups-service';
import { Dispatch } from 'redux';
-import { arrayPush, change, initialize } from 'redux-form';
+import { change, initialize, untouch } from 'redux-form';
import { RootState } from '~/store/store';
import { initUserProject, treePickerActions } from '~/store/tree-picker/tree-picker-actions';
import { ServiceRepository } from '~/services/services';
import { SearchView } from '~/store/search-bar/search-bar-reducer';
import { navigateTo, navigateToSearchResults } from '~/store/navigation/navigation-action';
import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
-import { PropertyValue, SearchBarAdvanceFormData } from '~/models/search-bar';
+import { PropertyValue, SearchBarAdvancedFormData } from '~/models/search-bar';
import * as _ from "lodash";
import { getModifiedKeysValues } from "~/common/objects";
import { activateSearchBarProject } from "~/store/search-bar/search-bar-tree-actions";
import { ListResults } from "~/services/common-service/common-service";
import * as parser from './search-query/arv-parser';
import { Keywords } from './search-query/arv-parser';
+import { Vocabulary, getTagKeyLabel, getTagValueLabel } from "~/models/vocabulary";
export const searchBarActions = unionize({
SET_CURRENT_VIEW: ofType<string>(),
CLOSE_SEARCH_VIEW: ofType<{}>(),
SET_SEARCH_RESULTS: ofType<GroupContentsResource[]>(),
SET_SEARCH_VALUE: ofType<string>(),
- SET_SAVED_QUERIES: ofType<SearchBarAdvanceFormData[]>(),
+ SET_SAVED_QUERIES: ofType<SearchBarAdvancedFormData[]>(),
SET_RECENT_QUERIES: ofType<string[]>(),
- UPDATE_SAVED_QUERY: ofType<SearchBarAdvanceFormData[]>(),
+ UPDATE_SAVED_QUERY: ofType<SearchBarAdvancedFormData[]>(),
SET_SELECTED_ITEM: ofType<string>(),
MOVE_UP: ofType<{}>(),
MOVE_DOWN: ofType<{}>(),
export type SearchBarActions = UnionOf<typeof searchBarActions>;
-export const SEARCH_BAR_ADVANCE_FORM_NAME = 'searchBarAdvanceFormName';
+export const SEARCH_BAR_ADVANCED_FORM_NAME = 'searchBarAdvancedFormName';
-export const SEARCH_BAR_ADVANCE_FORM_PICKER_ID = 'searchBarAdvanceFormPickerId';
+export const SEARCH_BAR_ADVANCED_FORM_PICKER_ID = 'searchBarAdvancedFormPickerId';
export const DEFAULT_SEARCH_DEBOUNCE = 1000;
}
};
-export const searchAdvanceData = (data: SearchBarAdvanceFormData) =>
+export const searchAdvancedData = (data: SearchBarAdvancedFormData) =>
async (dispatch: Dispatch, getState: () => RootState) => {
dispatch<any>(saveQuery(data));
const searchValue = getState().searchBar.searchValue;
dispatch(navigateToSearchResults(searchValue));
};
-export const setSearchValueFromAdvancedData = (data: SearchBarAdvanceFormData, prevData?: SearchBarAdvanceFormData) =>
+export const setSearchValueFromAdvancedData = (data: SearchBarAdvancedFormData, prevData?: SearchBarAdvancedFormData) =>
(dispatch: Dispatch, getState: () => RootState) => {
const searchValue = getState().searchBar.searchValue;
const value = getQueryFromAdvancedData({
dispatch(searchBarActions.SET_SEARCH_VALUE(value));
};
-export const setAdvancedDataFromSearchValue = (search: string) =>
+export const setAdvancedDataFromSearchValue = (search: string, vocabulary: Vocabulary) =>
async (dispatch: Dispatch) => {
- const data = getAdvancedDataFromQuery(search);
- dispatch<any>(initialize(SEARCH_BAR_ADVANCE_FORM_NAME, data));
+ const data = getAdvancedDataFromQuery(search, vocabulary);
+ dispatch<any>(initialize(SEARCH_BAR_ADVANCED_FORM_NAME, data));
if (data.projectUuid) {
await dispatch<any>(activateSearchBarProject(data.projectUuid));
- dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ pickerId: SEARCH_BAR_ADVANCE_FORM_PICKER_ID, id: data.projectUuid }));
+ dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ pickerId: SEARCH_BAR_ADVANCED_FORM_PICKER_ID, id: data.projectUuid }));
}
};
-const saveQuery = (data: SearchBarAdvanceFormData) =>
+const saveQuery = (data: SearchBarAdvancedFormData) =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
const savedQueries = services.searchService.getSavedQueries();
if (data.saveQuery && data.queryName) {
return savedSearchQueries || [];
};
-export const editSavedQuery = (data: SearchBarAdvanceFormData) =>
+export const editSavedQuery = (data: SearchBarAdvancedFormData) =>
(dispatch: Dispatch<any>) => {
dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.ADVANCED));
dispatch(searchBarActions.SET_SEARCH_VALUE(getQueryFromAdvancedData(data)));
- dispatch<any>(initialize(SEARCH_BAR_ADVANCE_FORM_NAME, data));
+ dispatch<any>(initialize(SEARCH_BAR_ADVANCED_FORM_NAME, data));
};
export const openSearchView = () =>
export const closeAdvanceView = () =>
(dispatch: Dispatch<any>) => {
dispatch(searchBarActions.SET_SEARCH_VALUE(''));
- dispatch(treePickerActions.DEACTIVATE_TREE_PICKER_NODE({ pickerId: SEARCH_BAR_ADVANCE_FORM_PICKER_ID }));
+ dispatch(treePickerActions.DEACTIVATE_TREE_PICKER_NODE({ pickerId: SEARCH_BAR_ADVANCED_FORM_PICKER_ID }));
dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC));
};
return value;
};
-export const getQueryFromAdvancedData = (data: SearchBarAdvanceFormData, prevData?: SearchBarAdvanceFormData) => {
+export const getQueryFromAdvancedData = (data: SearchBarAdvancedFormData, prevData?: SearchBarAdvancedFormData) => {
let value = '';
- const flatData = (data: SearchBarAdvanceFormData) => {
+ const flatData = (data: SearchBarAdvancedFormData) => {
const fo = {
searchValue: data.searchValue,
type: data.type,
dateFrom: data.dateFrom,
dateTo: data.dateTo,
};
- (data.properties || []).forEach(p => fo[`prop-"${p.key}"`] = `"${p.value}"`);
+ (data.properties || []).forEach(p => fo[`prop-"${p.keyID || p.key}"`] = `"${p.valueID || p.value}"`);
return fo;
};
['to', 'dateTo']
];
_.union(data.properties, prevData ? prevData.properties : [])
- .forEach(p => keyMap.push([`has:"${p.key}"`, `prop-"${p.key}"`]));
+ .forEach(p => keyMap.push([`has:"${p.keyID || p.key}"`, `prop-"${p.keyID || p.key}"`]));
if (prevData) {
const obj = getModifiedKeysValues(flatData(data), flatData(prevData));
value = buildQueryFromKeyMap({
searchValue: data.searchValue,
...obj
- } as SearchBarAdvanceFormData, keyMap, "reuse");
+ } as SearchBarAdvancedFormData, keyMap, "reuse");
} else {
value = buildQueryFromKeyMap(flatData(data), keyMap, "rebuild");
}
return value;
};
-export const getAdvancedDataFromQuery = (query: string): SearchBarAdvanceFormData => {
+export const getAdvancedDataFromQuery = (query: string, vocabulary?: Vocabulary): SearchBarAdvancedFormData => {
const { tokens, searchString } = parser.parseSearchQuery(query);
const getValue = parser.getValue(tokens);
return {
inTrash: parser.isTrashed(tokens),
dateFrom: getValue(Keywords.FROM) || '',
dateTo: getValue(Keywords.TO) || '',
- properties: parser.getProperties(tokens),
+ properties: vocabulary
+ ? parser.getProperties(tokens).map(
+ p => {
+ return {
+ keyID: p.key,
+ key: getTagKeyLabel(p.key, vocabulary),
+ valueID: p.value,
+ value: getTagValueLabel(p.key, p.value, vocabulary),
+ };
+ })
+ : parser.getProperties(tokens),
saveQuery: false,
queryName: ''
};
return date ? date : '';
};
-export const initAdvanceFormProjectsTree = () =>
+export const initAdvancedFormProjectsTree = () =>
(dispatch: Dispatch) => {
- dispatch<any>(initUserProject(SEARCH_BAR_ADVANCE_FORM_PICKER_ID));
+ dispatch<any>(initUserProject(SEARCH_BAR_ADVANCED_FORM_PICKER_ID));
};
-export const changeAdvanceFormProperty = (property: string, value: PropertyValue[] | string = '') =>
+export const changeAdvancedFormProperty = (propertyField: string, value: PropertyValue[] | string = '') =>
(dispatch: Dispatch) => {
- dispatch(change(SEARCH_BAR_ADVANCE_FORM_NAME, property, value));
+ dispatch(change(SEARCH_BAR_ADVANCED_FORM_NAME, propertyField, value));
};
-export const updateAdvanceFormProperties = (propertyValues: PropertyValue) =>
+export const resetAdvancedFormProperty = (propertyField: string) =>
(dispatch: Dispatch) => {
- dispatch(arrayPush(SEARCH_BAR_ADVANCE_FORM_NAME, 'properties', propertyValues));
+ dispatch(change(SEARCH_BAR_ADVANCED_FORM_NAME, propertyField, null));
+ dispatch(untouch(SEARCH_BAR_ADVANCED_FORM_NAME, propertyField));
};
export const moveUp = () =>
SearchBarActions
} from '~/store/search-bar/search-bar-actions';
import { GroupContentsResource } from '~/services/groups-service/groups-service';
-import { SearchBarAdvanceFormData } from '~/models/search-bar';
+import { SearchBarAdvancedFormData } from '~/models/search-bar';
type SearchResult = GroupContentsResource;
export type SearchBarSelectedItem = {
open: boolean;
searchResults: SearchResult[];
searchValue: string;
- savedQueries: SearchBarAdvanceFormData[];
+ savedQueries: SearchBarAdvancedFormData[];
recentQueries: string[];
selectedItem: SearchBarSelectedItem;
}
const makeSelectedItem = (id: string, query?: string): SearchBarSelectedItem => ({ id, query: query ? query : id });
-const makeQueryList = (recentQueries: string[], savedQueries: SearchBarAdvanceFormData[]) => {
+const makeQueryList = (recentQueries: string[], savedQueries: SearchBarAdvancedFormData[]) => {
const recentIds = recentQueries.map((q, idx) => makeSelectedItem(`RQ-${idx}-${q}`, q));
const savedIds = savedQueries.map((q, idx) => makeSelectedItem(`SQ-${idx}-${q.queryName}`, getQueryFromAdvancedData(q)));
return recentIds.concat(savedIds);
import { OrderBuilder } from "~/services/api/order-builder";
import { ProjectResource } from "~/models/project";
import { resourcesActions } from "~/store/resources/resources-actions";
-import { SEARCH_BAR_ADVANCE_FORM_PICKER_ID } from "~/store/search-bar/search-bar-actions";
+import { SEARCH_BAR_ADVANCED_FORM_PICKER_ID } from "~/store/search-bar/search-bar-actions";
const getSearchBarTreeNode = (id: string) => (treePicker: TreePicker) => {
- const searchTree = getTreePicker(SEARCH_BAR_ADVANCE_FORM_PICKER_ID)(treePicker);
+ const searchTree = getTreePicker(SEARCH_BAR_ADVANCED_FORM_PICKER_ID)(treePicker);
return searchTree
? getNode(id)(searchTree)
: undefined;
export const loadSearchBarTreeProjects = (projectUuid: string) =>
async (dispatch: Dispatch, getState: () => RootState) => {
- const treePicker = getTreePicker(SEARCH_BAR_ADVANCE_FORM_PICKER_ID)(getState().treePicker);
+ const treePicker = getTreePicker(SEARCH_BAR_ADVANCED_FORM_PICKER_ID)(getState().treePicker);
const node = treePicker ? getNode(projectUuid)(treePicker) : undefined;
if (node || projectUuid === '') {
await dispatch<any>(loadSearchBarProject(projectUuid));
};
export const getSearchBarTreeNodeAncestorsIds = (id: string) => (treePicker: TreePicker) => {
- const searchTree = getTreePicker(SEARCH_BAR_ADVANCE_FORM_PICKER_ID)(treePicker);
+ const searchTree = getTreePicker(SEARCH_BAR_ADVANCED_FORM_PICKER_ID)(treePicker);
return searchTree
? getNodeAncestorsIds(id)(searchTree)
: [];
...[],
...ancestors.map(ancestor => ancestor.uuid)
],
- pickerId: SEARCH_BAR_ADVANCE_FORM_PICKER_ID
+ pickerId: SEARCH_BAR_ADVANCED_FORM_PICKER_ID
}));
- dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId: SEARCH_BAR_ADVANCE_FORM_PICKER_ID }));
+ dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId: SEARCH_BAR_ADVANCED_FORM_PICKER_ID }));
};
export const expandSearchBarTreeItem = (id: string) =>
async (dispatch: Dispatch, getState: () => RootState) => {
const node = getSearchBarTreeNode(id)(getState().treePicker);
if (node && !node.expanded) {
- dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId: SEARCH_BAR_ADVANCE_FORM_PICKER_ID }));
+ dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId: SEARCH_BAR_ADVANCED_FORM_PICKER_ID }));
}
};
}
dispatch(treePickerActions.EXPAND_TREE_PICKER_NODES({
ids: getSearchBarTreeNodeAncestorsIds(id)(treePicker),
- pickerId: SEARCH_BAR_ADVANCE_FORM_PICKER_ID
+ pickerId: SEARCH_BAR_ADVANCED_FORM_PICKER_ID
}));
dispatch<any>(expandSearchBarTreeItem(id));
};
const loadSearchBarProject = (projectUuid: string) =>
async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => {
- dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: projectUuid, pickerId: SEARCH_BAR_ADVANCE_FORM_PICKER_ID }));
+ dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: projectUuid, pickerId: SEARCH_BAR_ADVANCED_FORM_PICKER_ID }));
const params = {
filters: new FilterBuilder()
.addEqual('ownerUuid', projectUuid)
const { items } = await services.projectService.list(params);
dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
id: projectUuid,
- pickerId: SEARCH_BAR_ADVANCE_FORM_PICKER_ID,
+ pickerId: SEARCH_BAR_ADVANCED_FORM_PICKER_ID,
nodes: items.map(item => initTreeNode({ id: item.uuid, value: item })),
}));
dispatch(resourcesActions.SET_RESOURCES(items));
// SPDX-License-Identifier: AGPL-3.0
import * as React from "react";
-import { Field, WrappedFieldProps, FieldArray, formValues } from 'redux-form';
+import { Field, WrappedFieldProps, FieldArray } from 'redux-form';
import { TextField, DateTextField } from "~/components/text-field/text-field";
import { CheckboxField } from '~/components/checkbox-field/checkbox-field';
import { NativeSelectField } from '~/components/select-field/select-field';
import { ResourceKind } from '~/models/resource';
import { HomeTreePicker } from '~/views-components/projects-tree-picker/home-tree-picker';
-import { SEARCH_BAR_ADVANCE_FORM_PICKER_ID } from '~/store/search-bar/search-bar-actions';
+import { SEARCH_BAR_ADVANCED_FORM_PICKER_ID } from '~/store/search-bar/search-bar-actions';
import { SearchBarAdvancedPropertiesView } from '~/views-components/search-bar/search-bar-advanced-properties-view';
import { TreeItem } from "~/components/tree/tree";
import { ProjectsTreePickerItem } from "~/views-components/projects-tree-picker/generic-projects-tree-picker";
-import { PropertyKeyInput } from '~/views-components/resource-properties-form/property-key-field';
-import { PropertyValueInput, PropertyValueFieldProps } from '~/views-components/resource-properties-form/property-value-field';
-import { VocabularyProp, connectVocabulary } from '~/views-components/resource-properties-form/property-field-common';
-import { compose } from 'redux';
+import { PropertyKeyField, } from '~/views-components/resource-properties-form/property-key-field';
+import { PropertyValueField } from '~/views-components/resource-properties-form/property-value-field';
import { connect } from "react-redux";
import { RootState } from "~/store/store";
const ProjectsPicker = (props: WrappedFieldProps) =>
<div style={{ height: '100px', display: 'flex', flexDirection: 'column', overflow: 'overlay' }}>
<HomeTreePicker
- pickerId={SEARCH_BAR_ADVANCE_FORM_PICKER_ID}
+ pickerId={SEARCH_BAR_ADVANCED_FORM_PICKER_ID}
toggleItemActive={
(_: any, { id }: TreeItem<ProjectsTreePickerItem>) => {
props.input.onChange(id);
name="properties"
component={SearchBarAdvancedPropertiesView} />;
-export const SearchBarKeyField = connectVocabulary(
- ({ vocabulary }: VocabularyProp) =>
- <Field
- name='key'
- component={PropertyKeyInput}
- vocabulary={vocabulary} />);
+export const SearchBarKeyField = () =>
+ <PropertyKeyField skipValidation={true} />;
-export const SearchBarValueField = compose(
- connectVocabulary,
- formValues({ propertyKey: 'key' })
-)(
- (props: PropertyValueFieldProps) =>
- <Field
- name='value'
- component={PropertyValueInput}
- {...props} />);
+export const SearchBarValueField = () =>
+ <PropertyValueField skipValidation={true} />;
export const SearchBarSaveSearchField = () =>
<Field
vocabulary: Vocabulary;
}
-export const mapStateToProps = (state: RootState): VocabularyProp => ({
+export interface ValidationProp {
+ skipValidation?: boolean;
+}
+
+export const mapStateToProps = (state: RootState, ownProps: ValidationProp): VocabularyProp & ValidationProp => ({
+ skipValidation: ownProps.skipValidation,
vocabulary: getVocabulary(state.properties),
});
value: input.value,
onChange: input.onChange,
items: ITEMS_PLACEHOLDER,
- renderSuggestion: (item:PropFieldSuggestion) => item.label,
+ renderSuggestion: (item: PropFieldSuggestion) => item.label,
error: hasError(meta),
helperText: getErrorMsg(meta),
};
{ dispatch }: WrappedFieldMetaProps,
{ onBlur, value }: WrappedFieldInputProps,
fieldValue: string) =>
- () => {
- dispatch(change(formName, fieldName, fieldValue));
- onBlur(value);
- };
+ () => {
+ dispatch(change(formName, fieldName, fieldValue));
+ onBlur(value);
+ };
// When selecting a property value, save its ID for later usage.
export const handleSelect = (
fieldName: string,
formName: string,
{ onChange }: WrappedFieldInputProps,
- { dispatch }: WrappedFieldMetaProps ) =>
- (item:PropFieldSuggestion) => {
- if (item) {
- onChange(item.label);
- dispatch(change(formName, fieldName, item.id));
- }
- };
+ { dispatch }: WrappedFieldMetaProps) =>
+ (item: PropFieldSuggestion) => {
+ if (item) {
+ onChange(item.label);
+ dispatch(change(formName, fieldName, item.id));
+ }
+ };
import { memoize } from 'lodash';
import { Autocomplete } from '~/components/autocomplete/autocomplete';
import { Vocabulary, getTags, getTagKeyID } from '~/models/vocabulary';
-import { handleSelect, handleBlur, connectVocabulary, VocabularyProp, buildProps } from '~/views-components/resource-properties-form/property-field-common';
+import { handleSelect, handleBlur, connectVocabulary, VocabularyProp, ValidationProp, buildProps } from '~/views-components/resource-properties-form/property-field-common';
import { TAG_KEY_VALIDATION } from '~/validators/validators';
import { escapeRegExp } from '~/common/regexp.ts';
export const PROPERTY_KEY_FIELD_ID = 'keyID';
export const PropertyKeyField = connectVocabulary(
- ({ vocabulary }: VocabularyProp) =>
+ ({ vocabulary, skipValidation }: VocabularyProp & ValidationProp) =>
<Field
name={PROPERTY_KEY_FIELD_NAME}
component={PropertyKeyInput}
vocabulary={vocabulary}
- validate={getValidation(vocabulary)} />
+ validate={skipValidation ? undefined : getValidation(vocabulary)} />
);
-export const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & VocabularyProp) =>
+const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & VocabularyProp) =>
<FormName children={data => (
<Autocomplete
label='Key'
onBlur={handleBlur(PROPERTY_KEY_FIELD_ID, data.form, props.meta, props.input, getTagKeyID(props.input.value, vocabulary))}
{...buildProps(props)}
/>
- )}/>;
+ )} />;
const getValidation = memoize(
(vocabulary: Vocabulary) =>
import { Autocomplete } from '~/components/autocomplete/autocomplete';
import { Vocabulary, isStrictTag, getTagValues, getTagValueID } from '~/models/vocabulary';
import { PROPERTY_KEY_FIELD_ID } from '~/views-components/resource-properties-form/property-key-field';
-import { handleSelect, handleBlur, VocabularyProp, connectVocabulary, buildProps } from '~/views-components/resource-properties-form/property-field-common';
+import { handleSelect, handleBlur, VocabularyProp, ValidationProp, connectVocabulary, buildProps } from '~/views-components/resource-properties-form/property-field-common';
import { TAG_VALUE_VALIDATION } from '~/validators/validators';
import { escapeRegExp } from '~/common/regexp.ts';
propertyKey: string;
}
-export type PropertyValueFieldProps = VocabularyProp & PropertyKeyProp;
+type PropertyValueFieldProps = VocabularyProp & PropertyKeyProp & ValidationProp;
export const PROPERTY_VALUE_FIELD_NAME = 'value';
export const PROPERTY_VALUE_FIELD_ID = 'valueID';
-export const PropertyValueField = compose(
+const connectVocabularyAndPropertyKey = compose(
connectVocabulary,
- formValues({ propertyKey: PROPERTY_KEY_FIELD_ID })
-)(
- (props: PropertyValueFieldProps) =>
+ formValues({ propertyKey: PROPERTY_KEY_FIELD_ID }),
+);
+
+export const PropertyValueField = connectVocabularyAndPropertyKey(
+ ({ skipValidation, ...props }: PropertyValueFieldProps) =>
<Field
name={PROPERTY_VALUE_FIELD_NAME}
component={PropertyValueInput}
- validate={getValidation(props)}
+ validate={skipValidation ? undefined : getValidation(props)}
{...props} />
);
-export const PropertyValueInput = ({ vocabulary, propertyKey, ...props }: WrappedFieldProps & PropertyValueFieldProps) =>
+const PropertyValueInput = ({ vocabulary, propertyKey, ...props }: WrappedFieldProps & PropertyValueFieldProps) =>
<FormName children={data => (
<Autocomplete
label='Value'
onBlur={handleBlur(PROPERTY_VALUE_FIELD_ID, data.form, props.meta, props.input, getTagValueID(propertyKey, props.input.value, vocabulary))}
{...buildProps(props)}
/>
- )}/>;
+ )} />;
const getValidation = (props: PropertyValueFieldProps) =>
isStrictTag(props.propertyKey, props.vocabulary)
const re = new RegExp(escapeRegExp(value), "i");
return getTagValues(tagName, vocabulary).filter(v => re.test(v.label) && v.label !== value);
};
-
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { Dispatch } from 'redux';
+import { Dispatch, compose } from 'redux';
import { connect } from 'react-redux';
import { InjectedFormProps, formValueSelector } from 'redux-form';
import { Grid, withStyles, StyleRulesCallback, WithStyles, Button } from '@material-ui/core';
import { RootState } from '~/store/store';
import {
- SEARCH_BAR_ADVANCE_FORM_NAME,
- changeAdvanceFormProperty,
- updateAdvanceFormProperties
+ SEARCH_BAR_ADVANCED_FORM_NAME,
+ changeAdvancedFormProperty,
+ resetAdvancedFormProperty
} from '~/store/search-bar/search-bar-actions';
import { PropertyValue } from '~/models/search-bar';
import { ArvadosTheme } from '~/common/custom-theme';
import { SearchBarKeyField, SearchBarValueField } from '~/views-components/form-fields/search-bar-form-fields';
import { Chips } from '~/components/chips/chips';
import { formatPropertyValue } from "~/common/formatters";
+import { Vocabulary } from '~/models/vocabulary';
+import { connectVocabulary } from '../resource-properties-form/property-field-common';
type CssRules = 'label' | 'button';
pristine: boolean;
propertyValues: PropertyValue;
fields: PropertyValue[];
+ vocabulary: Vocabulary;
}
interface SearchBarAdvancedPropertiesViewActionProps {
setProps: () => void;
- addProp: (propertyValues: PropertyValue) => void;
+ setProp: (propertyValues: PropertyValue, properties: PropertyValue[]) => void;
getAllFields: (propertyValues: PropertyValue[]) => PropertyValue[] | [];
}
& SearchBarAdvancedPropertiesViewActionProps
& InjectedFormProps & WithStyles<CssRules>;
-const selector = formValueSelector(SEARCH_BAR_ADVANCE_FORM_NAME);
+const selector = formValueSelector(SEARCH_BAR_ADVANCED_FORM_NAME);
const mapStateToProps = (state: RootState) => {
return {
- propertyValues: selector(state, 'key', 'value')
+ propertyValues: selector(state, 'key', 'value', 'keyID', 'valueID')
};
};
const mapDispatchToProps = (dispatch: Dispatch) => ({
setProps: (propertyValues: PropertyValue[]) => {
- dispatch<any>(changeAdvanceFormProperty('properties', propertyValues));
+ dispatch<any>(changeAdvancedFormProperty('properties', propertyValues));
},
- addProp: (propertyValues: PropertyValue) => {
- dispatch<any>(updateAdvanceFormProperties(propertyValues));
- dispatch<any>(changeAdvanceFormProperty('key'));
- dispatch<any>(changeAdvanceFormProperty('value'));
+ setProp: (propertyValue: PropertyValue, properties: PropertyValue[]) => {
+ dispatch<any>(changeAdvancedFormProperty(
+ 'properties',
+ [...properties.filter(e => e.keyID! !== propertyValue.keyID!), propertyValue]
+ ));
+ dispatch<any>(resetAdvancedFormProperty('key'));
+ dispatch<any>(resetAdvancedFormProperty('value'));
+ dispatch<any>(resetAdvancedFormProperty('keyID'));
+ dispatch<any>(resetAdvancedFormProperty('valueID'));
},
getAllFields: (fields: any) => {
return fields.getAll() || [];
}
});
-export const SearchBarAdvancedPropertiesView = connect(mapStateToProps, mapDispatchToProps)(
+export const SearchBarAdvancedPropertiesView = compose(
+ connectVocabulary,
+ connect(mapStateToProps, mapDispatchToProps))(
withStyles(styles)(
- ({ classes, fields, propertyValues, setProps, addProp, getAllFields }: SearchBarAdvancedPropertiesViewProps) =>
+ ({ classes, fields, propertyValues, setProps, setProp, getAllFields, vocabulary }: SearchBarAdvancedPropertiesViewProps) =>
<Grid container item xs={12} spacing={16}>
<Grid item xs={2} className={classes.label}>Properties</Grid>
<Grid item xs={4}>
<SearchBarValueField />
</Grid>
<Grid container item xs={2} justify='flex-end' alignItems="center">
- <Button className={classes.button} onClick={() => addProp(propertyValues)}
+ <Button className={classes.button} onClick={() => setProp(propertyValues, getAllFields(fields))}
color="primary"
size='small'
variant="contained"
<Chips values={getAllFields(fields)}
deletable
onChange={setProps}
- getLabel={(field: PropertyValue) => formatPropertyValue(field)} />
+ getLabel={(field: PropertyValue) => formatPropertyValue(field, vocabulary)} />
</Grid>
</Grid>
)
import { compose, Dispatch } from 'redux';
import { Paper, StyleRulesCallback, withStyles, WithStyles, Button, Grid, IconButton, CircularProgress } from '@material-ui/core';
import {
- SEARCH_BAR_ADVANCE_FORM_NAME, SEARCH_BAR_ADVANCE_FORM_PICKER_ID,
- searchAdvanceData,
+ SEARCH_BAR_ADVANCED_FORM_NAME, SEARCH_BAR_ADVANCED_FORM_PICKER_ID,
+ searchAdvancedData,
setSearchValueFromAdvancedData
} from '~/store/search-bar/search-bar-actions';
import { ArvadosTheme } from '~/common/custom-theme';
import { CloseIcon } from '~/components/icon/icon';
-import { SearchBarAdvanceFormData } from '~/models/search-bar';
+import { SearchBarAdvancedFormData } from '~/models/search-bar';
import {
SearchBarTypeField, SearchBarClusterField, SearchBarProjectField, SearchBarTrashField,
SearchBarDateFromField, SearchBarDateToField, SearchBarPropertiesField,
};
export const SearchBarAdvancedView = compose(
- reduxForm<SearchBarAdvanceFormData, SearchBarAdvancedViewProps>({
- form: SEARCH_BAR_ADVANCE_FORM_NAME,
+ reduxForm<SearchBarAdvancedFormData, SearchBarAdvancedViewProps>({
+ form: SEARCH_BAR_ADVANCED_FORM_NAME,
validate,
- onSubmit: (data: SearchBarAdvanceFormData, dispatch: Dispatch) => {
- dispatch<any>(searchAdvanceData(data));
- dispatch(reset(SEARCH_BAR_ADVANCE_FORM_NAME));
- dispatch(treePickerActions.DEACTIVATE_TREE_PICKER_NODE({ pickerId: SEARCH_BAR_ADVANCE_FORM_PICKER_ID }));
+ onSubmit: (data: SearchBarAdvancedFormData, dispatch: Dispatch) => {
+ dispatch<any>(searchAdvancedData(data));
+ dispatch(reset(SEARCH_BAR_ADVANCED_FORM_NAME));
+ dispatch(treePickerActions.DEACTIVATE_TREE_PICKER_NODE({ pickerId: SEARCH_BAR_ADVANCED_FORM_PICKER_ID }));
},
- onChange: (data: SearchBarAdvanceFormData, dispatch: Dispatch, props: any, prevData: SearchBarAdvanceFormData) => {
+ onChange: (data: SearchBarAdvancedFormData, dispatch: Dispatch, props: any, prevData: SearchBarAdvancedFormData) => {
dispatch<any>(setSearchValueFromAdvancedData(data, prevData));
},
}),
import { withStyles, WithStyles, StyleRulesCallback, List, ListItem, ListItemText, ListItemSecondaryAction, Tooltip, IconButton } from '@material-ui/core';
import { ArvadosTheme } from '~/common/custom-theme';
import { RemoveIcon, EditSavedQueryIcon } from '~/components/icon/icon';
-import { SearchBarAdvanceFormData } from '~/models/search-bar';
+import { SearchBarAdvancedFormData } from '~/models/search-bar';
import { SearchBarSelectedItem } from "~/store/search-bar/search-bar-reducer";
import { getQueryFromAdvancedData } from "~/store/search-bar/search-bar-actions";
});
export interface SearchBarSavedQueriesDataProps {
- savedQueries: SearchBarAdvanceFormData[];
+ savedQueries: SearchBarAdvancedFormData[];
selectedItem: SearchBarSelectedItem;
}
export interface SearchBarSavedQueriesActionProps {
onSearch: (searchValue: string) => void;
deleteSavedQuery: (id: number) => void;
- editSavedQuery: (data: SearchBarAdvanceFormData, id: number) => void;
+ editSavedQuery: (data: SearchBarAdvancedFormData, id: number) => void;
}
type SearchBarSavedQueriesProps = SearchBarSavedQueriesDataProps
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
+import { compose } from 'redux';
import {
IconButton,
Paper,
} from '~/views-components/search-bar/search-bar-advanced-view';
import { KEY_CODE_DOWN, KEY_CODE_ESC, KEY_CODE_UP, KEY_ENTER } from "~/common/codes";
import { debounce } from 'debounce';
+import { Vocabulary } from '~/models/vocabulary';
+import { connectVocabulary } from '../resource-properties-form/property-field-common';
type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'view';
currentView: string;
isPopoverOpen: boolean;
debounce?: number;
+ vocabulary?: Vocabulary;
}
export type SearchBarActionProps = SearchBarViewActionProps
loadRecentQueries: () => string[];
moveUp: () => void;
moveDown: () => void;
- setAdvancedDataFromSearchValue: (search: string) => void;
+ setAdvancedDataFromSearchValue: (search: string, vocabulary?: Vocabulary) => void;
}
type SearchBarViewProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
const handleDropdownClick = (e: React.MouseEvent, props: SearchBarViewProps) => {
e.stopPropagation();
- if (props.isPopoverOpen) {
- if (props.currentView === SearchView.ADVANCED) {
- props.closeView();
- } else {
- props.setAdvancedDataFromSearchValue(props.searchValue);
- props.onSetView(SearchView.ADVANCED);
- }
+ if (props.isPopoverOpen && props.currentView === SearchView.ADVANCED) {
+ props.closeView();
} else {
- props.setAdvancedDataFromSearchValue(props.searchValue);
+ props.setAdvancedDataFromSearchValue(props.searchValue, props.vocabulary);
props.onSetView(SearchView.ADVANCED);
}
};
-export const SearchBarView = withStyles(styles)(
+export const SearchBarView = compose(connectVocabulary, withStyles(styles))(
class extends React.Component<SearchBarViewProps> {
debouncedSearch = debounce(() => {
navigateToItem,
editSavedQuery,
changeData,
- submitData, moveUp, moveDown, setAdvancedDataFromSearchValue
+ submitData, moveUp, moveDown, setAdvancedDataFromSearchValue, SEARCH_BAR_ADVANCED_FORM_NAME
} from '~/store/search-bar/search-bar-actions';
import { SearchBarView, SearchBarActionProps, SearchBarDataProps } from '~/views-components/search-bar/search-bar-view';
-import { SearchBarAdvanceFormData } from '~/models/search-bar';
+import { SearchBarAdvancedFormData } from '~/models/search-bar';
+import { Vocabulary } from '~/models/vocabulary';
const mapStateToProps = ({ searchBar, form }: RootState): SearchBarDataProps => {
return {
searchResults: searchBar.searchResults,
selectedItem: searchBar.selectedItem,
savedQueries: searchBar.savedQueries,
- tags: form.searchBarAdvanceFormName,
- saveQuery: form.searchBarAdvanceFormName &&
- form.searchBarAdvanceFormName.values &&
- form.searchBarAdvanceFormName.values.saveQuery
+ tags: form[SEARCH_BAR_ADVANCED_FORM_NAME],
+ saveQuery: form[SEARCH_BAR_ADVANCED_FORM_NAME] &&
+ form[SEARCH_BAR_ADVANCED_FORM_NAME].values &&
+ form[SEARCH_BAR_ADVANCED_FORM_NAME].values!.saveQuery
};
};
deleteSavedQuery: (id: number) => dispatch<any>(deleteSavedQuery(id)),
openSearchView: () => dispatch<any>(openSearchView()),
navigateTo: (uuid: string) => dispatch<any>(navigateToItem(uuid)),
- editSavedQuery: (data: SearchBarAdvanceFormData) => dispatch<any>(editSavedQuery(data)),
+ editSavedQuery: (data: SearchBarAdvancedFormData) => dispatch<any>(editSavedQuery(data)),
moveUp: () => dispatch<any>(moveUp()),
moveDown: () => dispatch<any>(moveDown()),
- setAdvancedDataFromSearchValue: (search: string) => dispatch<any>(setAdvancedDataFromSearchValue(search))
+ setAdvancedDataFromSearchValue: (search: string, vocabulary: Vocabulary) => dispatch<any>(setAdvancedDataFromSearchValue(search, vocabulary))
});
export const SearchBar = connect(mapStateToProps, mapDispatchToProps)(SearchBarView);
import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
import { SearchResultsPanelView } from '~/views/search-results-panel/search-results-panel-view';
import { RootState } from '~/store/store';
-import { SearchBarAdvanceFormData } from '~/models/search-bar';
+import { SearchBarAdvancedFormData } from '~/models/search-bar';
import { User } from "~/models/user";
import { Config } from '~/common/config';
import { Session } from "~/models/session";
export interface SearchResultsPanelDataProps {
- data: SearchBarAdvanceFormData;
+ data: SearchBarAdvancedFormData;
user: User;
sessions: Session[];
remoteHostsConfig: { [key: string]: Config };