From 62d66aaaafecce911d6f94a74c4409802672a762 Mon Sep 17 00:00:00 2001 From: "Ezequiel Lopez ezequiel.lopez@graylog.com" Date: Wed, 24 Aug 2022 15:20:43 -0400 Subject: [PATCH 01/27] task: Migrates CachePicker and CachesContainer --- .../components/lookup-tables/CachePicker.tsx | 36 +++++----- .../lookup-tables/CachesContainer.jsx | 64 ----------------- .../lookup-tables/CachesContainer.tsx | 44 ++++++++++++ .../api/lookupTablesCachesAPI.ts | 29 ++++++++ .../lookup-tables/useLookupTableCachesAPI.ts | 68 +++++++++++++++++++ 5 files changed, 160 insertions(+), 81 deletions(-) delete mode 100644 graylog2-web-interface/src/components/lookup-tables/CachesContainer.jsx create mode 100644 graylog2-web-interface/src/components/lookup-tables/CachesContainer.tsx create mode 100644 graylog2-web-interface/src/hooks/lookup-tables/api/lookupTablesCachesAPI.ts create mode 100644 graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts diff --git a/graylog2-web-interface/src/components/lookup-tables/CachePicker.tsx b/graylog2-web-interface/src/components/lookup-tables/CachePicker.tsx index 2800d67ef3de..ac12e153a87f 100644 --- a/graylog2-web-interface/src/components/lookup-tables/CachePicker.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/CachePicker.tsx @@ -15,24 +15,34 @@ * . */ import React from 'react'; -import PropTypes from 'prop-types'; import { useField } from 'formik'; -import type { LookupTableCache } from 'src/logic/lookup-tables/types'; -import { defaultCompare as naturalSort } from 'logic/DefaultCompare'; +import type { LookupTableCache } from 'logic/lookup-tables/types'; import { Input } from 'components/bootstrap'; import { Select } from 'components/common'; type Props = { caches: LookupTableCache[], -} +}; + +type OptionsType = { + label: string, + value: string, +}; const CachePicker = ({ caches }: Props) => { - const [, { value, touched, error }, { setTouched, setValue }] = useField('cache_id'); - const sortedCaches = caches.map((cache) => { - return { value: cache.id, label: `${cache.title} (${cache.name})` }; - }).sort((a, b) => naturalSort(a.label.toLowerCase(), b.label.toLowerCase())); + const sortedCaches = React.useMemo(() => { + return caches.map((cache: LookupTableCache) => ( + { value: cache.id, label: `${cache.title} (${cache.name})` } + )).sort((a: OptionsType, b: OptionsType) => { + if (a.label.toLowerCase() > b.label.toLowerCase()) return 1; + if (a.label.toLowerCase() < b.label.toLowerCase()) return -1; + + return 0; + }); + }, [caches]); + const [, { value, touched, error }, { setTouched, setValue }] = useField('cache_id'); const errorMessage = touched ? error : ''; return ( @@ -42,7 +52,7 @@ const CachePicker = ({ caches }: Props) => { required autoFocus bsStyle={errorMessage ? 'error' : undefined} - help={errorMessage || 'Select an existing cache'} + help={errorMessage || 'Search by cache name'} labelClassName="col-sm-3" wrapperClassName="col-sm-9"> Date: Thu, 25 Aug 2022 14:06:58 -0400 Subject: [PATCH 05/27] task: Fixes bug when deleting last item in the last page --- .../src/components/common/PaginatedList.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/graylog2-web-interface/src/components/common/PaginatedList.tsx b/graylog2-web-interface/src/components/common/PaginatedList.tsx index 99f1531f1306..64be6e975d5d 100644 --- a/graylog2-web-interface/src/components/common/PaginatedList.tsx +++ b/graylog2-web-interface/src/components/common/PaginatedList.tsx @@ -79,10 +79,14 @@ const PaginatedList = ({ onChange(INITIAL_PAGE, newPageSize); }; - const _onChangePage = (pageNum: number) => { + const _onChangePage = React.useCallback((pageNum: number) => { setPagination({ currentPage: pageNum, currentPageSize }); onChange(pageNum, currentPageSize); - }; + }, [onChange, currentPageSize]); + + React.useEffect(() => { + if (numberPages > 0 && currentPage > numberPages) _onChangePage(numberPages); + }, [currentPage, numberPages, _onChangePage]); return ( <> From 9268d642ffcb342279fdf03ce7d0bc2b66410074 Mon Sep 17 00:00:00 2001 From: "Ezequiel Lopez ezequiel.lopez@graylog.com" Date: Thu, 25 Aug 2022 15:37:39 -0400 Subject: [PATCH 06/27] task: Adds types to useQuery return --- .../src/hooks/lookup-tables/api/types.ts | 26 +++++++++++++++++++ .../lookup-tables/useLookupTableCachesAPI.ts | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 graylog2-web-interface/src/hooks/lookup-tables/api/types.ts diff --git a/graylog2-web-interface/src/hooks/lookup-tables/api/types.ts b/graylog2-web-interface/src/hooks/lookup-tables/api/types.ts new file mode 100644 index 000000000000..03200885443f --- /dev/null +++ b/graylog2-web-interface/src/hooks/lookup-tables/api/types.ts @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import type { LookupTableCache } from 'logic/lookup-tables/types'; + +export type LUTCacheAPIResponseType = { + caches: LookupTableCache[], + count: number, + total: number, + page: number, + per_page: number, + query?: string, +}; diff --git a/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts b/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts index 74fd82d3ff8c..cf3113220213 100644 --- a/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts +++ b/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts @@ -19,6 +19,7 @@ import { useQuery } from 'react-query'; import UserNotification from 'util/UserNotification'; import { fetchAll } from './api/lookupTablesCachesAPI'; +import type { LUTCacheAPIResponseType } from './api/types'; type GetAllCachesType = { page?: number, @@ -27,7 +28,7 @@ type GetAllCachesType = { }; export const useGetAllCaches = ({ page, perPage, query }: GetAllCachesType = {}) => { - const { data, isLoading, error } = useQuery( + const { data, isLoading, error } = useQuery( ['all-caches', page, perPage, query], () => fetchAll(page, perPage, query), { From 50d854c2a3e54281defb99813da8c05250cab804 Mon Sep 17 00:00:00 2001 From: "Ezequiel Lopez ezequiel.lopez@graylog.com" Date: Thu, 25 Aug 2022 15:48:51 -0400 Subject: [PATCH 07/27] task: Changes key to follow ecma convetion --- .../src/hooks/lookup-tables/useLookupTableCachesAPI.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts b/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts index cf3113220213..8fdc1876dadb 100644 --- a/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts +++ b/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts @@ -43,7 +43,7 @@ export const useGetAllCaches = ({ page, perPage, query }: GetAllCachesType = {}) count: 0, total: 0, page: 1, - per_page: 10, + perPage: 10, query: null, }, }; @@ -54,7 +54,7 @@ export const useGetAllCaches = ({ page, perPage, query }: GetAllCachesType = {}) count: data.count, total: data.total, page: data.page, - per_page: data.per_page, + perPage: data.per_page, query: data.query, }, }; From 2c485e1315e3cde363f191ee4890380954319284 Mon Sep 17 00:00:00 2001 From: "Ezequiel Lopez ezequiel.lopez@graylog.com" Date: Fri, 26 Aug 2022 15:21:47 -0400 Subject: [PATCH 08/27] task: Popover only works if overview calls a function --- .../src/components/lookup-tables/CachesOverview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graylog2-web-interface/src/components/lookup-tables/CachesOverview.tsx b/graylog2-web-interface/src/components/lookup-tables/CachesOverview.tsx index b89964a943c4..5250ae00d644 100644 --- a/graylog2-web-interface/src/components/lookup-tables/CachesOverview.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/CachesOverview.tsx @@ -27,7 +27,7 @@ import type { LookupTableCache } from 'logic/lookup-tables/types'; import CacheTableEntry from './CacheTableEntry'; import Styles from './Overview.css'; -const HelpPopover = () => { +const getHelpPopover = () => { return ( { style={{ marginLeft: 5 }}> Create cache - }> + - - - - - - - - - - - - - - - - - {lookupTables} -
TitleDescriptionNameCacheData AdapterActions
- - - - - ); - } -} - -export default LookupTablesOverview; diff --git a/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx b/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx new file mode 100644 index 000000000000..ac87ba1b7dbb --- /dev/null +++ b/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; +import { useHistory } from 'react-router-dom'; + +import Routes from 'routing/Routes'; +import { Row, Col, Table, Popover, Button } from 'components/bootstrap'; +import { OverlayTrigger, PaginatedList, SearchForm, Icon } from 'components/common'; +import LUTTableEntry from 'components/lookup-tables/LUTTableEntry'; +import type { LookupTable, LookupTableAdapter, LookupTableCache } from 'logic/lookup-tables/types'; +import { LookupTablesActions } from 'stores/lookup-tables/LookupTablesStore'; + +import Styles from './Overview.css'; + +const getHelpPopover = () => { + return ( + +

Available search fields

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
idLookup Table ID
titleThe title of the lookup table
nameThe reference name of the lookup table
descriptionThe description of lookup table
+

Examples

+

+ Find lookup tables by parts of their names:
+ name:geoip
+ name:geo +

+

+ Searching without a field name matches against the title field:
+ geoip
is the same as
+ title:geoip +

+
+ ); +}; + +const getLookupTablesList = ( + table: LookupTable, + caches: LookupTableCache[], + dataAdapters: LookupTableAdapter[], + errorStates: { [key: string]: { [key: string]: string } }, +) => { + const lookupName = (id: string, map: LookupTableCache[] | LookupTableAdapter[]) => { + const empty = { title: 'none' }; + + if (!map) return empty; + + return map[id] || empty; + }; + + const lookupAdapterError = () => { + if (errorStates.dataAdapters && dataAdapters) { + const adapter = dataAdapters[table.data_adapter_id]; + + if (!adapter) return null; + + return errorStates.dataAdapters[adapter.name]; + } + + return null; + }; + + const cache = lookupName(table.cache_id, caches); + const dataAdapter = lookupName(table.data_adapter_id, dataAdapters); + const errors = { + table: errorStates.tables[table.name], + cache: null, + dataAdapter: lookupAdapterError(), + }; + + return ( + + ); +}; + +type OverviewProps = { + tables: LookupTable[], + caches: LookupTableCache[], + dataAdapters: LookupTableAdapter[], + pagination: { page: number, per_page: number, total: number, query?: string }, + errorStates: { [key: string]: { [key: string]: string } }, +}; + +const LookupTablesOverview = ({ tables, caches, dataAdapters, pagination, errorStates }: OverviewProps) => { + const history = useHistory(); + + React.useEffect(() => { + LookupTablesActions.searchPaginated(pagination.page, pagination.per_page); + }, [pagination]); + + const onPageChange = (newPage: number, newPerPage: number) => { + LookupTablesActions.searchPaginated(newPage, newPerPage, pagination.query); + }; + + const onSearch = (query: string, resetLoadingStateCb: () => void) => { + LookupTablesActions + .searchPaginated(pagination.page, pagination.per_page, query) + .then(resetLoadingStateCb); + }; + + const onReset = () => { + LookupTablesActions.searchPaginated(pagination.page, pagination.per_page); + }; + + const goCreateLUT = () => { + history.push(Routes.SYSTEM.LOOKUPTABLES.CREATE); + }; + + return ( + + +

+ Configured lookup tables +  {pagination.total} total +

+ + + + + + + + + + + + + + + + + + + {tables.map((table: LookupTable) => getLookupTablesList(table, caches, dataAdapters, errorStates))} +
TitleDescriptionNameCacheData AdapterActions
+
+ +
+ ); +}; + +export default LookupTablesOverview; diff --git a/graylog2-web-interface/src/hooks/lookup-tables/api/lookupTablesCachesAPI.ts b/graylog2-web-interface/src/hooks/lookup-tables/api/lookupTablesCachesAPI.ts index 36e953e007c4..30e3269e004e 100644 --- a/graylog2-web-interface/src/hooks/lookup-tables/api/lookupTablesCachesAPI.ts +++ b/graylog2-web-interface/src/hooks/lookup-tables/api/lookupTablesCachesAPI.ts @@ -22,8 +22,8 @@ const getURL = (path: string = '') => (URLUtils.qualifyUrl(`/system/lookup${path // eslint-disable-next-line import/prefer-default-export export const fetchAll = async (page = 1, perPage = 100, query = null) => { - let url = getURL(`/caches?page=${page}&per_page=${perPage}&sort=title&order=asc`); - if (query) url += `&query=${query}`; + let url = getURL(`/caches?sort=title&order=asc&page=${page}&per_page=${perPage}`); + if (query) url += `&query=${encodeURIComponent(query)}`; return fetch('GET', url); }; diff --git a/graylog2-web-interface/src/pages/LUTCachesPage.jsx b/graylog2-web-interface/src/pages/LUTCachesPage.jsx index d2ec55be8c07..cb45bf036c4d 100644 --- a/graylog2-web-interface/src/pages/LUTCachesPage.jsx +++ b/graylog2-web-interface/src/pages/LUTCachesPage.jsx @@ -54,14 +54,17 @@ class LUTCachesPage extends React.Component { } }; + // eslint-disable-next-line class-methods-use-this _saved = () => { history.push(Routes.SYSTEM.LOOKUPTABLES.CACHES.OVERVIEW); }; + // eslint-disable-next-line class-methods-use-this _isCreating = ({ action }) => { return action === 'create'; }; + // eslint-disable-next-line class-methods-use-this _validateCache = (adapter) => { LookupTableCachesActions.validate(adapter); }; @@ -73,7 +76,6 @@ class LUTCachesPage extends React.Component { validationErrors, types, caches, - pagination, } = this.props; let content; const isShowing = action === 'show'; @@ -114,8 +116,7 @@ class LUTCachesPage extends React.Component { content = ; } else { content = ( - + ); } @@ -153,7 +154,6 @@ LUTCachesPage.propTypes = { types: PropTypes.object, caches: PropTypes.array, location: PropTypes.object.isRequired, - pagination: PropTypes.object.isRequired, action: PropTypes.string, }; diff --git a/graylog2-web-interface/src/pages/LUTTablesPage.jsx b/graylog2-web-interface/src/pages/LUTTablesPage.jsx index 98eb8bc32dcc..53d08179cefd 100644 --- a/graylog2-web-interface/src/pages/LUTTablesPage.jsx +++ b/graylog2-web-interface/src/pages/LUTTablesPage.jsx @@ -92,18 +92,18 @@ class LUTTablesPage extends React.Component { } }; - // eslint-disable-next-line + // eslint-disable-next-line class-methods-use-this _saved = () => { // reset detail state history.push(Routes.SYSTEM.LOOKUPTABLES.OVERVIEW); }; - // eslint-disable-next-line + // eslint-disable-next-line class-methods-use-this _isCreating = ({ action }) => { return action === 'create'; }; - // eslint-disable-next-line + // eslint-disable-next-line class-methods-use-this _validateTable = (table) => { LookupTablesActions.validate(table); }; diff --git a/graylog2-web-interface/src/stores/lookup-tables/LookupTablesStore.ts b/graylog2-web-interface/src/stores/lookup-tables/LookupTablesStore.ts index 7cf2d7f17de7..0e61a1bac559 100644 --- a/graylog2-web-interface/src/stores/lookup-tables/LookupTablesStore.ts +++ b/graylog2-web-interface/src/stores/lookup-tables/LookupTablesStore.ts @@ -74,7 +74,7 @@ type LookupTablesStoreState = { } type LookupTableActionsType = { - searchPaginated: (page: number, perPage: number, query: string, resolve: boolean) => Promise, + searchPaginated: (page: number, perPage: number, query?: string, resolve?: boolean) => Promise, reloadPage: () => Promise, get: (idOrName: string) => Promise, create: (table: LookupTable) => Promise, @@ -161,7 +161,7 @@ export const LookupTablesStore = singletonStore( }, searchPaginated(page: number, perPage: number, query: string, resolve: boolean = true) { - const url = this._url(PaginationURL('tables', page, perPage, query, { resolve })); + const url = this._url(PaginationURL('tables', page, perPage, query, { resolve, sort: 'title', order: 'asc' })); const promise = fetch('GET', url); promise.then((response) => { From 2f20a1a9f5ba8a665a59a23100f1000f6487804e Mon Sep 17 00:00:00 2001 From: "Ezequiel Lopez ezequiel.lopez@graylog.com" Date: Mon, 29 Aug 2022 11:49:37 -0400 Subject: [PATCH 10/27] task: Migrates LookupTableCreate --- ...pTableCreate.jsx => LookupTableCreate.tsx} | 50 +++++++------------ .../lookup-tables/LookupTablesOverview.tsx | 14 ++---- .../api/lookupTablesCachesAPI.ts | 2 +- .../src/util/PaginationURL.ts | 2 +- 4 files changed, 24 insertions(+), 44 deletions(-) rename graylog2-web-interface/src/components/lookup-tables/{LookupTableCreate.jsx => LookupTableCreate.tsx} (51%) diff --git a/graylog2-web-interface/src/components/lookup-tables/LookupTableCreate.jsx b/graylog2-web-interface/src/components/lookup-tables/LookupTableCreate.tsx similarity index 51% rename from graylog2-web-interface/src/components/lookup-tables/LookupTableCreate.jsx rename to graylog2-web-interface/src/components/lookup-tables/LookupTableCreate.tsx index 88115cb08686..9921c0e16508 100644 --- a/graylog2-web-interface/src/components/lookup-tables/LookupTableCreate.jsx +++ b/graylog2-web-interface/src/components/lookup-tables/LookupTableCreate.tsx @@ -14,42 +14,28 @@ * along with this program. If not, see * . */ -import PropTypes from 'prop-types'; -import React from 'react'; +import * as React from 'react'; import { Row, Col } from 'components/bootstrap'; import { LookupTableForm } from 'components/lookup-tables'; -class LookupTableCreate extends React.Component { - static propTypes = { - saved: PropTypes.func.isRequired, - validate: PropTypes.func, - validationErrors: PropTypes.object, - }; +type Props = { + saved: () => void, + validate: () => void, + validationErrors: { [key: string]: { [key: string]: string } } +}; - static defaultProps = { - validate: null, - validationErrors: {}, - }; - - state = { - table: undefined, - }; - - render() { - return ( -
- - - - - -
- ); - } -} +const LookupTableCreate = ({ saved, validate, validationErrors }: Props) => { + return ( + + + + + + ); +}; export default LookupTableCreate; diff --git a/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx b/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx index ac87ba1b7dbb..96140e1837ba 100644 --- a/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx @@ -125,22 +125,16 @@ type OverviewProps = { const LookupTablesOverview = ({ tables, caches, dataAdapters, pagination, errorStates }: OverviewProps) => { const history = useHistory(); - React.useEffect(() => { - LookupTablesActions.searchPaginated(pagination.page, pagination.per_page); - }, [pagination]); - const onPageChange = (newPage: number, newPerPage: number) => { LookupTablesActions.searchPaginated(newPage, newPerPage, pagination.query); }; - const onSearch = (query: string, resetLoadingStateCb: () => void) => { - LookupTablesActions - .searchPaginated(pagination.page, pagination.per_page, query) - .then(resetLoadingStateCb); + const onSearch = (query: string) => { + LookupTablesActions.searchPaginated(1, pagination.per_page, query); }; const onReset = () => { - LookupTablesActions.searchPaginated(pagination.page, pagination.per_page); + LookupTablesActions.searchPaginated(1, pagination.per_page, null); }; const goCreateLUT = () => { @@ -158,7 +152,7 @@ const LookupTablesOverview = ({ tables, caches, dataAdapters, pagination, errorS pageSize={pagination.per_page} onChange={onPageChange} totalItems={pagination.total}> - + - - - - - - - - - - - - - - - - {dataAdapterEntries} -
TitleDescriptionNameThroughputActions
- - - - - ); - } -} - -export default DataAdaptersOverview; diff --git a/graylog2-web-interface/src/components/lookup-tables/DataAdaptersOverview.tsx b/graylog2-web-interface/src/components/lookup-tables/DataAdaptersOverview.tsx new file mode 100644 index 000000000000..38905927137d --- /dev/null +++ b/graylog2-web-interface/src/components/lookup-tables/DataAdaptersOverview.tsx @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; +import { useHistory } from 'react-router-dom'; +import { useQueryClient } from 'react-query'; + +import Routes from 'routing/Routes'; +import { Row, Col, Table, Popover, Button } from 'components/bootstrap'; +import { OverlayTrigger, PaginatedList, SearchForm, Spinner, Icon } from 'components/common'; +import { useGetAllDataAdapters, useGetDataAdapterErrors } from 'hooks/lookup-tables/useLookupTableDataAdaptersAPI'; +import type { LookupTableAdapter } from 'logic/lookup-tables/types'; + +import DataAdapterTableEntry from './DataAdapterTableEntry'; +import Styles from './Overview.css'; + +const getHelpPopover = () => { + return ( + +

Available search fields

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
idData Adapter ID
titleThe title of the data adapter
nameThe reference name of the data adapter
descriptionThe description of data adapter
+

Example

+

+ Find data adapters by parts of their names:
+ name:geoip
+ name:geo +

+

+ Searching without a field name matches against the title field:
+ geoip
is the same as
+ title:geoip +

+
+ ); +}; + +const DataAdaptersOverview = () => { + const history = useHistory(); + const queryClient = useQueryClient(); + + const [localPagination, setLocalPagination] = React.useState({ page: 1, perPage: 10, query: null }); + const { dataAdapters, pagination, loadingDataAdapters } = useGetAllDataAdapters(localPagination); + const { dataAdapterErrors, loadingDataAdaptersErrors } = useGetDataAdapterErrors(dataAdapters); + + const onPageChange = (newPage: number, newPerPage: number) => { + setLocalPagination({ ...localPagination, page: newPage, perPage: newPerPage }); + }; + + const onSearch = (newQuery: string) => { + setLocalPagination({ ...localPagination, page: 1, query: newQuery }); + }; + + const onReset = () => { + setLocalPagination({ ...localPagination, query: null }); + }; + + const onDelete = () => { + queryClient.invalidateQueries('all-data-adapters'); + }; + + const toCreateView = () => { + history.push(Routes.SYSTEM.LOOKUPTABLES.DATA_ADAPTERS.CREATE); + }; + + return (loadingDataAdapters || loadingDataAdaptersErrors) ? : ( + + +

+ Configured lookup Data Adapters +   + {pagination.total} total + +

+ + + + + + + + + + + + + + + + + + {dataAdapters.map((adapter: LookupTableAdapter) => ( + + ))} +
TitleDescriptionNameThroughputActions
+
+ +
+ ); +}; + +export default DataAdaptersOverview; diff --git a/graylog2-web-interface/src/hooks/lookup-tables/api/lookupTablesCachesAPI.ts b/graylog2-web-interface/src/hooks/lookup-tables/api/cachesAPI.ts similarity index 100% rename from graylog2-web-interface/src/hooks/lookup-tables/api/lookupTablesCachesAPI.ts rename to graylog2-web-interface/src/hooks/lookup-tables/api/cachesAPI.ts diff --git a/graylog2-web-interface/src/hooks/lookup-tables/api/dataAdaptersAPI.ts b/graylog2-web-interface/src/hooks/lookup-tables/api/dataAdaptersAPI.ts new file mode 100644 index 000000000000..15ec445ecd96 --- /dev/null +++ b/graylog2-web-interface/src/hooks/lookup-tables/api/dataAdaptersAPI.ts @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ + +import fetch from 'logic/rest/FetchProvider'; +import URLUtils from 'util/URLUtils'; +import type { LookupTableAdapter } from 'src/logic/lookup-tables/types'; + +const getURL = (path: string = '') => (URLUtils.qualifyUrl(`/system/lookup${path}`)); + +// eslint-disable-next-line import/prefer-default-export +export const fetchAll = async (page = 1, perPage = 100, query = null) => { + let url = getURL(`/adapters?sort=title&order=asc&page=${page}&per_page=${perPage}`); + if (query) url += `&query=${encodeURI(query)}`; + + return fetch('GET', url); +}; + +export const fetchErrors = async (dataAdapters: LookupTableAdapter[]) => { + const dataAdapterNames = dataAdapters.map((adapter: LookupTableAdapter) => adapter.name); + + return fetch('POST', getURL('/errorstates'), { data_adapters : dataAdapterNames }); +}; diff --git a/graylog2-web-interface/src/hooks/lookup-tables/api/types.ts b/graylog2-web-interface/src/hooks/lookup-tables/api/types.ts index 03200885443f..effdc61212bc 100644 --- a/graylog2-web-interface/src/hooks/lookup-tables/api/types.ts +++ b/graylog2-web-interface/src/hooks/lookup-tables/api/types.ts @@ -14,13 +14,26 @@ * along with this program. If not, see * . */ -import type { LookupTableCache } from 'logic/lookup-tables/types'; +import type { LookupTableCache, LookupTableAdapter } from 'logic/lookup-tables/types'; -export type LUTCacheAPIResponseType = { - caches: LookupTableCache[], +type paginatedResposeType = { count: number, total: number, page: number, per_page: number, query?: string, }; + +export type LUTErrorsAPIResponseType = { + tables: { [key: string]: string } + caches: { [key: string]: string } + data_adapters: { [key: string]: string } +}; + +export type LUTCacheAPIResponseType = paginatedResposeType & { + caches: LookupTableCache[], +}; + +export type LUTDataAdapterAPIResponseType = paginatedResposeType & { + data_adapters: LookupTableAdapter[], +}; diff --git a/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts b/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts index 8fdc1876dadb..3670f21b3446 100644 --- a/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts +++ b/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableCachesAPI.ts @@ -18,7 +18,7 @@ import { useQuery } from 'react-query'; import UserNotification from 'util/UserNotification'; -import { fetchAll } from './api/lookupTablesCachesAPI'; +import { fetchAll } from './api/cachesAPI'; import type { LUTCacheAPIResponseType } from './api/types'; type GetAllCachesType = { @@ -27,6 +27,7 @@ type GetAllCachesType = { query?: string, }; +// eslint-disable-next-line import/prefer-default-export export const useGetAllCaches = ({ page, perPage, query }: GetAllCachesType = {}) => { const { data, isLoading, error } = useQuery( ['all-caches', page, perPage, query], @@ -65,5 +66,3 @@ export const useGetAllCaches = ({ page, perPage, query }: GetAllCachesType = {}) loadingCaches: isLoading, }; }; - -export const self = {}; diff --git a/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableDataAdaptersAPI.ts b/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableDataAdaptersAPI.ts new file mode 100644 index 000000000000..c0cfeb966ad3 --- /dev/null +++ b/graylog2-web-interface/src/hooks/lookup-tables/useLookupTableDataAdaptersAPI.ts @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import { useQuery } from 'react-query'; +import type { LookupTableAdapter } from 'logic/lookup-tables/types'; + +import UserNotification from 'util/UserNotification'; + +import { fetchAll, fetchErrors } from './api/dataAdaptersAPI'; +import type { LUTDataAdapterAPIResponseType, LUTErrorsAPIResponseType } from './api/types'; + +type GetAllDataAdaptersType = { + page?: number, + perPage?: number, + query?: string, +}; + +export const useGetAllDataAdapters = ({ page, perPage, query }: GetAllDataAdaptersType = {}) => { + const { data, isLoading, error } = useQuery( + ['all-data-adapters', page, perPage, query], + () => fetchAll(page, perPage, query), + { + onError: () => UserNotification.error(error.message), + retry: 2, + }, + ); + + const defaultData = { + dataAdapters: [], + pagination: { + count: 0, + total: 0, + page: 1, + perPage: 10, + query: null, + }, + }; + + const { dataAdapters, pagination } = isLoading ? defaultData : { + dataAdapters: data.data_adapters, + pagination: { + count: data.count, + total: data.total, + page: data.page, + perPage: data.per_page, + query: data.query, + }, + }; + + return { + dataAdapters, + pagination, + loadingDataAdapters: isLoading, + }; +}; + +export const useGetDataAdapterErrors = (dataAdapters: LookupTableAdapter[] = []) => { + const { data, isLoading, error } = useQuery( + ['errors-data-adapters', dataAdapters], + () => fetchErrors(dataAdapters), + { + onError: () => UserNotification.error(error.message), + retry: 1, + }, + ); + + return { + dataAdapterErrors: data?.data_adapters || {}, + loadingDataAdaptersErrors: isLoading, + }; +}; From 3636a5198802ed9b419728afa890b78382c3fcda Mon Sep 17 00:00:00 2001 From: "Ezequiel Lopez ezequiel.lopez@graylog.com" Date: Wed, 31 Aug 2022 16:05:32 -0400 Subject: [PATCH 14/27] task: Updates DataAdapterCreate --- .../components/lookup-tables/CacheCreate.tsx | 4 +- .../lookup-tables/DataAdapterCreate.jsx | 131 ----------------- ...te.test.jsx => DataAdapterCreate.test.tsx} | 28 +++- .../lookup-tables/DataAdapterCreate.tsx | 139 ++++++++++++++++++ .../lookup-tables/DataAdapterForm.jsx | 4 +- .../src/logic/lookup-tables/types.ts | 10 ++ .../src/pages/LUTDataAdaptersPage.jsx | 13 +- 7 files changed, 181 insertions(+), 148 deletions(-) delete mode 100644 graylog2-web-interface/src/components/lookup-tables/DataAdapterCreate.jsx rename graylog2-web-interface/src/components/lookup-tables/{DataAdapterCreate.test.jsx => DataAdapterCreate.test.tsx} (73%) create mode 100644 graylog2-web-interface/src/components/lookup-tables/DataAdapterCreate.tsx diff --git a/graylog2-web-interface/src/components/lookup-tables/CacheCreate.tsx b/graylog2-web-interface/src/components/lookup-tables/CacheCreate.tsx index 71e6a821ca09..4761adfe2e21 100644 --- a/graylog2-web-interface/src/components/lookup-tables/CacheCreate.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/CacheCreate.tsx @@ -15,8 +15,8 @@ * . */ import * as React from 'react'; -import type { LookupTableCache, validationErrorsType } from 'src/logic/lookup-tables/types'; +import type { LookupTableCache, validationErrorsType, LUTTypesType } from 'logic/lookup-tables/types'; import usePluginEntities from 'hooks/usePluginEntities'; import { Row, Col, Input } from 'components/bootstrap'; import { Select } from 'components/common'; @@ -37,7 +37,7 @@ type OptionType = { value: string, label: string } type Props = { saved: () => void, - types: TypesType[], + types: LUTTypesType, validate: () => void, validationErrors: validationErrorsType, }; diff --git a/graylog2-web-interface/src/components/lookup-tables/DataAdapterCreate.jsx b/graylog2-web-interface/src/components/lookup-tables/DataAdapterCreate.jsx deleted file mode 100644 index 47158c1261a0..000000000000 --- a/graylog2-web-interface/src/components/lookup-tables/DataAdapterCreate.jsx +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2020 Graylog, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * . - */ -import PropTypes from 'prop-types'; -import React from 'react'; -import { PluginStore } from 'graylog-web-plugin/plugin'; - -import { defaultCompare as naturalSort } from 'logic/DefaultCompare'; -import { Select } from 'components/common'; -import { Row, Col, Input } from 'components/bootstrap'; -import { DataAdapterForm } from 'components/lookup-tables'; -import ObjectUtils from 'util/ObjectUtils'; - -class DataAdapterCreate extends React.Component { - static propTypes = { - saved: PropTypes.func.isRequired, - types: PropTypes.object.isRequired, - validate: PropTypes.func, - validationErrors: PropTypes.object, - }; - - static defaultProps = { - validate: null, - validationErrors: {}, - }; - - constructor(props) { - super(props); - - this.state = { - dataAdapter: undefined, - type: undefined, - }; - } - - _onTypeSelect = (adapterType) => { - const { types } = this.props; - - this.setState({ - type: adapterType, - dataAdapter: { - id: null, - title: '', - name: '', - description: '', - config: ObjectUtils.clone(types[adapterType].default_config), - }, - }); - }; - - render() { - const { - types, - validate, - validationErrors, - saved, - } = this.props; - const { type, dataAdapter } = this.state; - const adapterPlugins = {}; - - PluginStore.exports('lookupTableAdapters').forEach((p) => { - adapterPlugins[p.type] = p; - }); - - const sortedAdapters = Object.keys(types).map((key) => { - const typeItem = types[key]; - - if (adapterPlugins[typeItem.type] === undefined) { - // eslint-disable-next-line no-console - console.error(`Plugin component for data adapter type ${typeItem.type} is missing - invalid or missing plugin?`); - - return { value: typeItem.type, disabled: true, label: `${typeItem.type} - missing or invalid plugin` }; - } - - return { value: typeItem.type, label: adapterPlugins[typeItem.type].displayName }; - }).sort((a, b) => naturalSort(a.label.toLowerCase(), b.label.toLowerCase())); - - return ( -
- - -
{}}> - - + { this._input = ref; }} - wrapperClassName="col-sm-9" /> - - - - - - - - {configFieldSet} -
- - - - - -
- - - {documentationColumn} -
- - ); - } -} - -export default DataAdapterForm; diff --git a/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx b/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx index b842a1ea6399..37c2d616ec15 100644 --- a/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx @@ -18,7 +18,6 @@ import * as React from 'react'; import _ from 'lodash'; import { Formik, Form } from 'formik'; -import ConfigFieldSet from './adapters/ConfigFieldSet'; import usePluginEntities from 'hooks/usePluginEntities'; import type { LookupTableAdapter, LookupTableDataAdapterConfig, validationErrorsType } from 'logic/lookup-tables/types'; import { Col, Row } from 'components/bootstrap'; @@ -28,6 +27,8 @@ import useScopePermissions from 'hooks/useScopePermissions'; import history from 'util/History'; import Routes from 'routing/Routes'; +import AdapterConfigFieldset from './adapters/AdapterConfigFieldset'; + type TitleProps = { title: string, typeName: string, @@ -68,9 +69,6 @@ type Props = { const DataAdapterForm = ({ type, title, saved, create, dataAdapter, validate, validationErrors }: Props) => { const [generateName, setGenerateName] = React.useState(create); - const [localDataAdapter, setLocalDataAdapter] = React.useState(_.cloneDeep(dataAdapter)); - React.useEffect(() => setLocalDataAdapter(_.cloneDeep(dataAdapter)), [dataAdapter]); - const [formErrors, setFormErrors] = React.useState(_.cloneDeep(validationErrors)); React.useEffect(() => setFormErrors(_.cloneDeep(validationErrors)), [validationErrors]); @@ -98,15 +96,19 @@ const DataAdapterForm = ({ type, title, saved, create, dataAdapter, validate, va }); }; - const handleValidation = (values: LookupTableAdapter) => { + const validateForm = (values: LookupTableAdapter) => { const errors: validationErrorsType = {}; + const requiredFieldNames = [...document.querySelectorAll('input:required')].map((input: HTMLInputElement) => input.name); - validate(values); - if (!values.title) errors.title = ['Required']; - if (!values.name) errors.name = ['Required']; + requiredFieldNames.forEach((name: string) => { + if (name in values && !values[name]) errors[name] = ['Required']; + if (name in values.config && !values.config[name]) errors[name] = ['Required']; + }); - setFormErrors({ ...validationErrors, ...errors }); - return { ...validationErrors, ...errors }; + if (Object.keys(errors).length === 0) validate(values); + setFormErrors(errors); + + return errors; }; const handleTTLUpdate = (values: LookupTableAdapter, setValues: any) => (value: number, unit: string, enabled: boolean) => { @@ -127,24 +129,24 @@ const DataAdapterForm = ({ type, title, saved, create, dataAdapter, validate, va }; const handleConfigInputChange = (values: LookupTableAdapter, setValues: any) => (event: React.BaseSyntheticEvent) => { - const auxConf = { ...localDataAdapter.config }; - auxConf[event.target.name] = event.target.type === 'checkbox' ? event.target.checked : event.target.value; - setValues({ ...values, config: auxConf }) - setLocalDataAdapter({ ...localDataAdapter, config: auxConf }); + const newValues = { ...values }; + newValues.config[event.target.name] = event.target.type === 'checkbox' ? event.target.checked : event.target.value; + + validateForm(newValues); + setValues(newValues); }; const handleTimeUnitInputChange = (values: LookupTableAdapter, setValues: any) => (newConfig: LookupTableDataAdapterConfig) => { - const auxAdapter = { ...localDataAdapter }; - auxAdapter.config = { ...newConfig }; - setValues({ ...values, config: newConfig }) - setLocalDataAdapter(auxAdapter); + const newValues = { ...values, config: newConfig }; + validateForm(newValues); + setValues(newValues); }; const getValidationMessage = (fieldName: string, defaultText: string) => { - if (validationErrors[fieldName]) { + if (formErrors[fieldName]) { return (
- {defaultText} {validationErrors[fieldName][0]} + {defaultText} {formErrors[fieldName][0]}
); } @@ -153,7 +155,7 @@ const DataAdapterForm = ({ type, title, saved, create, dataAdapter, validate, va }; const getValidationState = (fieldName: string) => { - return validationErrors[fieldName] ? 'error' : null; + return formErrors[fieldName] ? 'error' : null; }; const onCancel = () => history.push(Routes.SYSTEM.LOOKUPTABLES.DATA_ADAPTERS.OVERVIEW); @@ -164,10 +166,8 @@ const DataAdapterForm = ({ type, title, saved, create, dataAdapter, validate, va <Row> <Col lg={6}> - <Formik initialValues={localDataAdapter} - validate={handleValidation} - validateOnBlur - validateOnChange={false} + <Formik initialValues={dataAdapter} + validate={validateForm} validateOnMount={!create} onSubmit={handleSubmit} enableReinitialize> @@ -215,13 +215,13 @@ const DataAdapterForm = ({ type, title, saved, create, dataAdapter, validate, va labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> </fieldset> - <ConfigFieldSet formComponent={plugin.formComponent} - config={localDataAdapter.config} - handleFormEvent={handleConfigInputChange(values, setValues)} - updateConfig={handleTimeUnitInputChange(values, setValues)} - validationMessage={getValidationMessage} - validationState={getValidationState} - setDisableFormSubmission={false} /> + <AdapterConfigFieldset formComponent={plugin.formComponent} + config={values.config} + handleFormEvent={handleConfigInputChange(values, setValues)} + updateConfig={handleTimeUnitInputChange(values, setValues)} + validationMessage={getValidationMessage} + validationState={getValidationState} + setDisableFormSubmission={false} /> <fieldset> <Row> <Col mdOffset={3} sm={12}> diff --git a/graylog2-web-interface/src/components/lookup-tables/adapters/ConfigFieldSet.tsx b/graylog2-web-interface/src/components/lookup-tables/adapters/AdapterConfigFieldset.tsx similarity index 84% rename from graylog2-web-interface/src/components/lookup-tables/adapters/ConfigFieldSet.tsx rename to graylog2-web-interface/src/components/lookup-tables/adapters/AdapterConfigFieldset.tsx index 241f65747352..799c9a5296af 100644 --- a/graylog2-web-interface/src/components/lookup-tables/adapters/ConfigFieldSet.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/adapters/AdapterConfigFieldset.tsx @@ -18,7 +18,7 @@ import * as React from 'react'; import type { LookupTableDataAdapterConfig } from 'logic/lookup-tables/types'; -type ConfigFieldSetAttributes = { +type ConfigFieldsetAttributes = { config?: LookupTableDataAdapterConfig, handleFormEvent?: (arg: React.BaseSyntheticEvent) => void, updateConfig?: (newConfig: LookupTableDataAdapterConfig) => void, @@ -27,11 +27,11 @@ type ConfigFieldSetAttributes = { setDisableFormSubmission?: boolean, }; -type ConfigFieldSetProps = ConfigFieldSetAttributes & { +type ConfigFieldsetProps = ConfigFieldsetAttributes & { formComponent: React.FC, }; -const ConfigFieldSet = ({ +const AdapterConfigFieldset = ({ formComponent, config, handleFormEvent, @@ -39,8 +39,8 @@ const ConfigFieldSet = ({ validationMessage, validationState, setDisableFormSubmission, -}: ConfigFieldSetProps) => { - return React.createElement<ConfigFieldSetAttributes>( +}: ConfigFieldsetProps) => { + return React.createElement<ConfigFieldsetAttributes>( formComponent, { config, handleFormEvent, @@ -52,7 +52,7 @@ const ConfigFieldSet = ({ ); }; -ConfigFieldSet.defaultProps = { +AdapterConfigFieldset.defaultProps = { config: { type: 'none' }, handleFormEvent: () => {}, updateConfig: () => {}, @@ -61,4 +61,4 @@ ConfigFieldSet.defaultProps = { setDisableFormSubmission: false, }; -export default ConfigFieldSet; +export default AdapterConfigFieldset; diff --git a/graylog2-web-interface/src/components/lookup-tables/adapters/CSVFileAdapterFieldSet.tsx b/graylog2-web-interface/src/components/lookup-tables/adapters/CSVFileAdapterFieldSet.tsx index 155b158526c8..bd5fd1ec5f09 100644 --- a/graylog2-web-interface/src/components/lookup-tables/adapters/CSVFileAdapterFieldSet.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/adapters/CSVFileAdapterFieldSet.tsx @@ -46,7 +46,8 @@ const CSVFileAdapterFieldSet = ({ config, handleFormEvent, validationState, vali label="Check interval" required onChange={handleFormEvent} - help="The interval to check if the CSV file needs a reload. (in seconds)" + help={validationMessage('check_interval', 'The interval to check if the CSV file needs a reload. (in seconds)')} + bsStyle={validationState('check_interval')} value={config.check_interval} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -56,7 +57,8 @@ const CSVFileAdapterFieldSet = ({ config, handleFormEvent, validationState, vali label="Separator" required onChange={handleFormEvent} - help="The delimiter to use for separating entries." + help={validationMessage('separator', 'The delimiter to use for separating entries.')} + bsStyle={validationState('separator')} value={config.separator} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -66,7 +68,8 @@ const CSVFileAdapterFieldSet = ({ config, handleFormEvent, validationState, vali label="Quote character" required onChange={handleFormEvent} - help="The character to use for quoted elements." + help={validationMessage('quotechar', 'The character to use for quoted elements.')} + bsStyle={validationState('quotechar')} value={config.quotechar} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -76,7 +79,8 @@ const CSVFileAdapterFieldSet = ({ config, handleFormEvent, validationState, vali label="Key column" required onChange={handleFormEvent} - help="The column name that should be used for the key lookup." + help={validationMessage('key_column', 'The column name that should be used for the key lookup.')} + bsStyle={validationState('key_column')} value={config.key_column} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -86,7 +90,8 @@ const CSVFileAdapterFieldSet = ({ config, handleFormEvent, validationState, vali label="Value column" required onChange={handleFormEvent} - help="The column name that should be used as the value for a key." + help={validationMessage('value_column', 'The column name that should be used as the value for a key.')} + bsStyle={validationState('value_column')} value={config.value_column} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> diff --git a/graylog2-web-interface/src/components/lookup-tables/adapters/DSVHTTPAdapterFieldSet.tsx b/graylog2-web-interface/src/components/lookup-tables/adapters/DSVHTTPAdapterFieldSet.tsx index 321cd75f06b9..2730f0e7eddd 100644 --- a/graylog2-web-interface/src/components/lookup-tables/adapters/DSVHTTPAdapterFieldSet.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/adapters/DSVHTTPAdapterFieldSet.tsx @@ -44,7 +44,8 @@ const DSVHTTPAdapterFieldSet = ({ handleFormEvent, validationState, validationMe label="Refresh interval" required onChange={handleFormEvent} - help="The interval to check if the DSV file needs a reload. (in seconds)" + help={validationMessage('refresh_interval', 'The interval to check if the DSV file needs a reload. (in seconds)')} + bsStyle={validationState('refresh_interval')} value={config.refresh_interval} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -54,7 +55,8 @@ const DSVHTTPAdapterFieldSet = ({ handleFormEvent, validationState, validationMe label="Separator" required onChange={handleFormEvent} - help="The delimiter to use for separating columns of entries." + help={validationMessage('separator', 'The delimiter to use for separating columns of entries.')} + bsStyle={validationState('separator')} value={config.separator} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -64,7 +66,8 @@ const DSVHTTPAdapterFieldSet = ({ handleFormEvent, validationState, validationMe label="Line Separator" required onChange={handleFormEvent} - help="The delimiter to use for separating lines." + help={validationMessage('line_separator', 'The delimiter to use for separating lines.')} + bsStyle={validationState('line_separator')} value={config.line_separator} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -74,7 +77,8 @@ const DSVHTTPAdapterFieldSet = ({ handleFormEvent, validationState, validationMe label="Quote character" required onChange={handleFormEvent} - help="The character to use for quoted elements." + help={validationMessage('quotechar', 'The character to use for quoted elements.')} + bsStyle={validationState('quotechar')} value={config.quotechar} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -84,7 +88,8 @@ const DSVHTTPAdapterFieldSet = ({ handleFormEvent, validationState, validationMe label="Ignore characters" required onChange={handleFormEvent} - help="Ignore lines starting with these characters." + help={validationMessage('ignorechar', 'Ignore lines starting with these characters.')} + bsStyle={validationState('ignorechar')} value={config.ignorechar} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -94,7 +99,8 @@ const DSVHTTPAdapterFieldSet = ({ handleFormEvent, validationState, validationMe label="Key column" required onChange={handleFormEvent} - help="The column number that should be used for the key lookup." + help={validationMessage('key_column', 'The column number that should be used for the key lookup.')} + bsStyle={validationState('key_column')} value={config.key_column} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> @@ -104,7 +110,8 @@ const DSVHTTPAdapterFieldSet = ({ handleFormEvent, validationState, validationMe label="Value column" required onChange={handleFormEvent} - help="The column number that should be used as the value for a key." + help={validationMessage('value_column', 'The column number that should be used as the value for a key.')} + bsStyle={validationState('value_column')} value={config.value_column} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> diff --git a/graylog2-web-interface/src/components/lookup-tables/adapters/DnsAdapterFieldSet.tsx b/graylog2-web-interface/src/components/lookup-tables/adapters/DnsAdapterFieldSet.tsx index d1485c633bfc..918bb5d6cc3c 100644 --- a/graylog2-web-interface/src/components/lookup-tables/adapters/DnsAdapterFieldSet.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/adapters/DnsAdapterFieldSet.tsx @@ -82,10 +82,7 @@ const DnsAdapterFieldSet = ({ config, updateConfig, handleFormEvent, validationS name="server_ips" label="DNS Server IP Address" onChange={handleFormEvent} - help={validationMessage( - 'server_ips', - 'An optional comma-separated list of DNS server IP addresses.', - )} + help={validationMessage('server_ips', 'An optional comma-separated list of DNS server IP addresses.')} bsStyle={validationState('server_ips')} value={config.server_ips} labelClassName="col-sm-3" @@ -96,10 +93,7 @@ const DnsAdapterFieldSet = ({ config, updateConfig, handleFormEvent, validationS label="DNS Request Timeout" required onChange={handleFormEvent} - help={validationMessage( - 'request_timeout', - 'DNS request timeout in milliseconds.', - )} + help={validationMessage('request_timeout', 'DNS request timeout in milliseconds.')} bsStyle={validationState('request_timeout')} value={config.request_timeout} labelClassName="col-sm-3" diff --git a/graylog2-web-interface/src/components/lookup-tables/adapters/HTTPJSONPathAdapterFieldSet.tsx b/graylog2-web-interface/src/components/lookup-tables/adapters/HTTPJSONPathAdapterFieldSet.tsx index dc86c375c79c..32f2e5f9d9be 100644 --- a/graylog2-web-interface/src/components/lookup-tables/adapters/HTTPJSONPathAdapterFieldSet.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/adapters/HTTPJSONPathAdapterFieldSet.tsx @@ -89,7 +89,8 @@ class HTTPJSONPathAdapterFieldSet extends React.Component<Props> { label="HTTP User-Agent" required onChange={handleFormEvent} - help="The User-Agent header to use for the HTTP request." + help={validationMessage('user_agent', 'The User-Agent header to use for the HTTP request.')} + bsStyle={validationState('user_agent')} value={config.user_agent} labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> From 560486a76b6034242c6172dd6413c3fdec35bf36 Mon Sep 17 00:00:00 2001 From: "Ezequiel Lopez ezequiel.lopez@graylog.com" <ezequiel.lopez@graylog.com> Date: Mon, 10 Oct 2022 15:43:03 -0400 Subject: [PATCH 25/27] task: removs extra space --- .../src/components/lookup-tables/DataAdapterForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx b/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx index 37c2d616ec15..fdcb0fe7c9f7 100644 --- a/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx @@ -202,7 +202,6 @@ const DataAdapterForm = ({ type, title, saved, create, dataAdapter, validate, va } labelClassName="col-sm-3" wrapperClassName="col-sm-9" /> - <TimeUnitInput label="Custom Error TTL" help="Define a custom TTL for caching erroneous results. Otherwise the default of 5 seconds is used" update={handleTTLUpdate(values, setValues)} From 4b06276671c4fa03fcac76277deb279e92f95100 Mon Sep 17 00:00:00 2001 From: "Ezequiel Lopez ezequiel.lopez@graylog.com" <ezequiel.lopez@graylog.com> Date: Mon, 10 Oct 2022 16:30:32 -0400 Subject: [PATCH 26/27] task: Changes the text used to search for input --- .../src/components/lookup-tables/CacheForm.test.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graylog2-web-interface/src/components/lookup-tables/CacheForm.test.tsx b/graylog2-web-interface/src/components/lookup-tables/CacheForm.test.tsx index 9277dfae6d35..8df30eb9bf5c 100644 --- a/graylog2-web-interface/src/components/lookup-tables/CacheForm.test.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/CacheForm.test.tsx @@ -116,7 +116,7 @@ describe('CacheForm', () => { renderedCache({ scope: 'DEFAULT', inCache: cache, withConfig: false }); - const titleInput = screen.queryByLabelText('* Title'); + const titleInput = screen.queryByLabelText('Title'); fireEvent.blur(titleInput); const requiredErrorMessages = await screen.findAllByText('Required'); @@ -138,7 +138,7 @@ describe('CacheForm', () => { validationErrors: { name: ['The cache name is already in use.'] }, }); - const titleInput = screen.queryByLabelText('* Title'); + const titleInput = screen.queryByLabelText('Title'); fireEvent.blur(titleInput); const dupNameError = await screen.findByText('The cache name is already in use.'); @@ -162,8 +162,8 @@ describe('CacheForm', () => { saved: mockSaved, }); - const titleEle = await screen.findByLabelText('* Title'); - const nameEle = await screen.findByLabelText('* Name'); + const titleEle = await screen.findByLabelText('Title'); + const nameEle = await screen.findByLabelText('Name'); const submitButton = await findSubmitButton(); fireEvent.change(titleEle, { target: { value: '' } }); @@ -191,8 +191,8 @@ describe('CacheForm', () => { saved: mockSaved, }); - const titleEle = await screen.findByLabelText('* Title'); - const nameEle = await screen.findByLabelText('* Name'); + const titleEle = await screen.findByLabelText('Title'); + const nameEle = await screen.findByLabelText('Name'); const submitButton = await findSubmitButton(); fireEvent.change(titleEle, { target: { value: 'Test title' } }); From c7519e042cb5205a2ba070409f038871f57cf7b6 Mon Sep 17 00:00:00 2001 From: "Ezequiel Lopez ezequiel.lopez@graylog.com" <ezequiel.lopez@graylog.com> Date: Tue, 11 Oct 2022 15:33:43 -0400 Subject: [PATCH 27/27] task: fixes bugs with error message type --- .../src/components/common/InputDescription.tsx | 17 ++--------------- .../components/lookup-tables/CachesOverview.tsx | 12 ------------ .../lookup-tables/DataAdapterForm.tsx | 3 ++- .../lookup-tables/DataAdaptersOverview.tsx | 12 ------------ .../lookup-tables/LookupTablesOverview.tsx | 13 ------------- 5 files changed, 4 insertions(+), 53 deletions(-) diff --git a/graylog2-web-interface/src/components/common/InputDescription.tsx b/graylog2-web-interface/src/components/common/InputDescription.tsx index 0bc1872a9f26..c00693ee1079 100644 --- a/graylog2-web-interface/src/components/common/InputDescription.tsx +++ b/graylog2-web-interface/src/components/common/InputDescription.tsx @@ -15,7 +15,6 @@ * <http://www.mongodb.com/licensing/server-side-public-license>. */ import * as React from 'react'; -import PropTypes from 'prop-types'; import styled from 'styled-components'; import { HelpBlock } from 'components/bootstrap'; @@ -30,8 +29,8 @@ const HelpMessage = styled.span<{ hasError: boolean }>(({ theme, hasError }) => type Props = { className?: string, - error?: React.ReactElement, - help?: React.ReactNode, + error?: React.ReactElement | string, + help?: React.ReactNode | string, }; /** @@ -60,18 +59,6 @@ const InputDescription = ({ className, error, help }: Props) => { ); }; -InputDescription.propTypes = { - className: PropTypes.string, - error: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.string, - ]), - help: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.string, - ]), -}; - InputDescription.defaultProps = { className: undefined, error: undefined, diff --git a/graylog2-web-interface/src/components/lookup-tables/CachesOverview.tsx b/graylog2-web-interface/src/components/lookup-tables/CachesOverview.tsx index 86802ae43219..fc8adac81871 100644 --- a/graylog2-web-interface/src/components/lookup-tables/CachesOverview.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/CachesOverview.tsx @@ -15,10 +15,8 @@ * <http://www.mongodb.com/licensing/server-side-public-license>. */ import * as React from 'react'; -import { useHistory } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; -import Routes from 'routing/Routes'; import { Row, Col, Table, Popover, Button } from 'components/bootstrap'; import { OverlayTrigger, PaginatedList, SearchForm, Spinner, Icon } from 'components/common'; import { useGetAllCaches } from 'hooks/lookup-tables/useLookupTableCachesAPI'; @@ -75,7 +73,6 @@ const getHelpPopover = () => { }; const CachesOverview = () => { - const history = useHistory(); const queryClient = useQueryClient(); const [localPagination, setLocalPagination] = React.useState({ page: 1, perPage: 10, query: null }); @@ -97,10 +94,6 @@ const CachesOverview = () => { queryClient.invalidateQueries(['all-caches']); }; - const toCreateView = () => { - history.push(Routes.SYSTEM.LOOKUPTABLES.CACHES.CREATE); - }; - return ( <Row className="content"> <Col md={12}> @@ -112,11 +105,6 @@ const CachesOverview = () => { onChange={onPageChange} totalItems={pagination.total}> <SearchForm onSearch={onSearch} onReset={onReset}> - <Button bsStyle="success" - onClick={toCreateView} - style={{ marginLeft: 5 }}> - Create cache - </Button> <OverlayTrigger trigger="click" rootClose placement="right" overlay={getHelpPopover()}> <Button bsStyle="link" className={Styles.searchHelpButton}> diff --git a/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx b/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx index fdcb0fe7c9f7..200c8e0c8cab 100644 --- a/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/DataAdapterForm.tsx @@ -105,8 +105,9 @@ const DataAdapterForm = ({ type, title, saved, create, dataAdapter, validate, va if (name in values.config && !values.config[name]) errors[name] = ['Required']; }); + setFormErrors({ ...validationErrors, ...errors }); + if (Object.keys(errors).length === 0) validate(values); - setFormErrors(errors); return errors; }; diff --git a/graylog2-web-interface/src/components/lookup-tables/DataAdaptersOverview.tsx b/graylog2-web-interface/src/components/lookup-tables/DataAdaptersOverview.tsx index 7f389259135a..231a6e7c25b2 100644 --- a/graylog2-web-interface/src/components/lookup-tables/DataAdaptersOverview.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/DataAdaptersOverview.tsx @@ -15,10 +15,8 @@ * <http://www.mongodb.com/licensing/server-side-public-license>. */ import * as React from 'react'; -import { useHistory } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; -import Routes from 'routing/Routes'; import { Row, Col, Table, Popover, Button } from 'components/bootstrap'; import { OverlayTrigger, PaginatedList, SearchForm, Spinner, Icon } from 'components/common'; import { useGetAllDataAdapters, useGetDataAdapterErrors } from 'hooks/lookup-tables/useLookupTableDataAdaptersAPI'; @@ -73,7 +71,6 @@ const getHelpPopover = () => { }; const DataAdaptersOverview = () => { - const history = useHistory(); const queryClient = useQueryClient(); const [localPagination, setLocalPagination] = React.useState({ page: 1, perPage: 10, query: null }); @@ -96,10 +93,6 @@ const DataAdaptersOverview = () => { queryClient.invalidateQueries(['all-data-adapters']); }; - const toCreateView = () => { - history.push(Routes.SYSTEM.LOOKUPTABLES.DATA_ADAPTERS.CREATE); - }; - return ( <Row className="content"> <Col md={12}> @@ -114,11 +107,6 @@ const DataAdaptersOverview = () => { onChange={onPageChange} totalItems={pagination.total}> <SearchForm onSearch={onSearch} onReset={onReset}> - <Button bsStyle="success" - onClick={toCreateView} - style={{ marginLeft: 5 }}> - Create data adapter - </Button> <OverlayTrigger trigger="click" rootClose placement="right" overlay={getHelpPopover()}> <Button bsStyle="link" className={Styles.searchHelpButton}> diff --git a/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx b/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx index 96140e1837ba..bcaffacf64b0 100644 --- a/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx +++ b/graylog2-web-interface/src/components/lookup-tables/LookupTablesOverview.tsx @@ -15,9 +15,7 @@ * <http://www.mongodb.com/licensing/server-side-public-license>. */ import * as React from 'react'; -import { useHistory } from 'react-router-dom'; -import Routes from 'routing/Routes'; import { Row, Col, Table, Popover, Button } from 'components/bootstrap'; import { OverlayTrigger, PaginatedList, SearchForm, Icon } from 'components/common'; import LUTTableEntry from 'components/lookup-tables/LUTTableEntry'; @@ -123,8 +121,6 @@ type OverviewProps = { }; const LookupTablesOverview = ({ tables, caches, dataAdapters, pagination, errorStates }: OverviewProps) => { - const history = useHistory(); - const onPageChange = (newPage: number, newPerPage: number) => { LookupTablesActions.searchPaginated(newPage, newPerPage, pagination.query); }; @@ -137,10 +133,6 @@ const LookupTablesOverview = ({ tables, caches, dataAdapters, pagination, errorS LookupTablesActions.searchPaginated(1, pagination.per_page, null); }; - const goCreateLUT = () => { - history.push(Routes.SYSTEM.LOOKUPTABLES.CREATE); - }; - return ( <Row className="content"> <Col md={12}> @@ -153,11 +145,6 @@ const LookupTablesOverview = ({ tables, caches, dataAdapters, pagination, errorS onChange={onPageChange} totalItems={pagination.total}> <SearchForm onSearch={onSearch} onReset={onReset}> - <Button bsStyle="success" - style={{ marginLeft: 5 }} - onClick={goCreateLUT}> - Create lookup table - </Button> <OverlayTrigger trigger="click" rootClose placement="right" overlay={getHelpPopover()}> <Button bsStyle="link" className={Styles.searchHelpButton}><Icon name="question-circle" fixedWidth /></Button> </OverlayTrigger>