mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[App Search] Set up Curations routes & complete 'Edit Query' action in Analytics tables (#91052)
* Set up Curations routes * Update EngineRouter/Nav with Curations * Set up Curations find_or_create API * [bug] Fix view action not working correctly for "" query * Add Edit query action - to call find_or_create curation API & navigate to curation page + fix copy string, only just noticed this :doh: * Add/update unit tests for action column - Refactor out into a single shared test helper file that both AnalyticsTable and RecentQueriesTable simply calls & runs (instead of copying and pasting the same tests twice into 2 diff files) - note: test file can't be `.test.tsx` or Jest tries to automatically run it, which we don't want Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a1a9769f83
commit
f2e4cce0a2
14 changed files with 297 additions and 42 deletions
|
@ -5,18 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mountWithIntl, mockKibanaValues } from '../../../../../__mocks__';
|
||||
import { mountWithIntl } from '../../../../../__mocks__';
|
||||
import '../../../../__mocks__/engine_logic.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiBasicTable, EuiBadge, EuiEmptyPrompt } from '@elastic/eui';
|
||||
|
||||
import { runActionColumnTests } from './shared_columns_tests';
|
||||
|
||||
import { AnalyticsTable } from './';
|
||||
|
||||
describe('AnalyticsTable', () => {
|
||||
const { navigateToUrl } = mockKibanaValues;
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'some search',
|
||||
|
@ -69,18 +69,9 @@ describe('AnalyticsTable', () => {
|
|||
expect(tableContent).toContain('0');
|
||||
});
|
||||
|
||||
it('renders an action column', () => {
|
||||
describe('renders an action column', () => {
|
||||
const wrapper = mountWithIntl(<AnalyticsTable items={items} />);
|
||||
const viewQuery = wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').first();
|
||||
const editQuery = wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first();
|
||||
|
||||
viewQuery.simulate('click');
|
||||
expect(navigateToUrl).toHaveBeenCalledWith(
|
||||
'/engines/some-engine/analytics/query_detail/some%20search'
|
||||
);
|
||||
|
||||
editQuery.simulate('click');
|
||||
// TODO
|
||||
runActionColumnTests(wrapper);
|
||||
});
|
||||
|
||||
it('renders an empty prompt if no items are passed', () => {
|
||||
|
|
|
@ -5,18 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mountWithIntl, mockKibanaValues } from '../../../../../__mocks__';
|
||||
import { mountWithIntl } from '../../../../../__mocks__';
|
||||
import '../../../../__mocks__/engine_logic.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiBasicTable, EuiBadge, EuiEmptyPrompt } from '@elastic/eui';
|
||||
|
||||
import { runActionColumnTests } from './shared_columns_tests';
|
||||
|
||||
import { RecentQueriesTable } from './';
|
||||
|
||||
describe('RecentQueriesTable', () => {
|
||||
const { navigateToUrl } = mockKibanaValues;
|
||||
|
||||
const items = [
|
||||
{
|
||||
query_string: 'some search',
|
||||
|
@ -63,18 +63,9 @@ describe('RecentQueriesTable', () => {
|
|||
expect(tableContent).toContain('3');
|
||||
});
|
||||
|
||||
it('renders an action column', () => {
|
||||
describe('renders an action column', () => {
|
||||
const wrapper = mountWithIntl(<RecentQueriesTable items={items} />);
|
||||
const viewQuery = wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').first();
|
||||
const editQuery = wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first();
|
||||
|
||||
viewQuery.simulate('click');
|
||||
expect(navigateToUrl).toHaveBeenCalledWith(
|
||||
'/engines/some-engine/analytics/query_detail/some%20search'
|
||||
);
|
||||
|
||||
editQuery.simulate('click');
|
||||
// TODO
|
||||
runActionColumnTests(wrapper);
|
||||
});
|
||||
|
||||
it('renders an empty prompt if no items are passed', () => {
|
||||
|
|
|
@ -9,10 +9,12 @@ import React from 'react';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { flashAPIErrors } from '../../../../../shared/flash_messages';
|
||||
import { HttpLogic } from '../../../../../shared/http';
|
||||
import { KibanaLogic } from '../../../../../shared/kibana';
|
||||
import { EuiLinkTo } from '../../../../../shared/react_router_helpers';
|
||||
import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH } from '../../../../routes';
|
||||
import { generateEnginePath } from '../../../engine';
|
||||
import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH, ENGINE_CURATION_PATH } from '../../../../routes';
|
||||
import { generateEnginePath, EngineLogic } from '../../../engine';
|
||||
import { Query, RecentQuery } from '../../types';
|
||||
|
||||
import { InlineTagsList } from './inline_tags_list';
|
||||
|
@ -63,7 +65,7 @@ export const ACTIONS_COLUMN = {
|
|||
onClick: (item: Query | RecentQuery) => {
|
||||
const { navigateToUrl } = KibanaLogic.values;
|
||||
|
||||
const query = (item as Query).key || (item as RecentQuery).query_string;
|
||||
const query = (item as Query).key || (item as RecentQuery).query_string || '""';
|
||||
navigateToUrl(generateEnginePath(ENGINE_ANALYTICS_QUERY_DETAIL_PATH, { query }));
|
||||
},
|
||||
'data-test-subj': 'AnalyticsTableViewQueryButton',
|
||||
|
@ -74,12 +76,25 @@ export const ACTIONS_COLUMN = {
|
|||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.analytics.table.editTooltip',
|
||||
{ defaultMessage: 'Edit query analytics' }
|
||||
{ defaultMessage: 'Edit query' }
|
||||
),
|
||||
type: 'icon',
|
||||
icon: 'pencil',
|
||||
onClick: () => {
|
||||
// TODO: CurationsLogic
|
||||
onClick: async (item: Query | RecentQuery) => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { navigateToUrl } = KibanaLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
|
||||
try {
|
||||
const query = (item as Query).key || (item as RecentQuery).query_string || '""';
|
||||
const response = await http.get(
|
||||
`/api/app_search/engines/${engineName}/curations/find_or_create`,
|
||||
{ query: { query } }
|
||||
);
|
||||
navigateToUrl(generateEnginePath(ENGINE_CURATION_PATH, { curationId: response.id }));
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
'data-test-subj': 'AnalyticsTableEditQueryButton',
|
||||
},
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
mockHttpValues,
|
||||
mockKibanaValues,
|
||||
mockFlashMessageHelpers,
|
||||
} from '../../../../../__mocks__';
|
||||
import '../../../../__mocks__/engine_logic.mock';
|
||||
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
|
||||
import { nextTick } from '@kbn/test/jest';
|
||||
|
||||
export const runActionColumnTests = (wrapper: ReactWrapper) => {
|
||||
const { http } = mockHttpValues;
|
||||
const { navigateToUrl } = mockKibanaValues;
|
||||
const { flashAPIErrors } = mockFlashMessageHelpers;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('view action', () => {
|
||||
it('navigates to the query detail view', () => {
|
||||
wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').first().simulate('click');
|
||||
|
||||
expect(navigateToUrl).toHaveBeenCalledWith(
|
||||
'/engines/some-engine/analytics/query_detail/some%20search'
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to "" for the empty query', () => {
|
||||
wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').last().simulate('click');
|
||||
expect(navigateToUrl).toHaveBeenCalledWith(
|
||||
'/engines/some-engine/analytics/query_detail/%22%22'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit action', () => {
|
||||
it('calls the find_or_create curation API, then navigates the user to the curation', async () => {
|
||||
http.get.mockReturnValue(Promise.resolve({ id: 'cur-123456789' }));
|
||||
wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first().simulate('click');
|
||||
await nextTick();
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/api/app_search/engines/some-engine/curations/find_or_create',
|
||||
{
|
||||
query: { query: 'some search' },
|
||||
}
|
||||
);
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-123456789');
|
||||
});
|
||||
|
||||
it('falls back to "" for the empty query', async () => {
|
||||
http.get.mockReturnValue(Promise.resolve({ id: 'cur-987654321' }));
|
||||
wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').last().simulate('click');
|
||||
await nextTick();
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/api/app_search/engines/some-engine/curations/find_or_create',
|
||||
{
|
||||
query: { query: '""' },
|
||||
}
|
||||
);
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-987654321');
|
||||
});
|
||||
|
||||
it('handles API errors', async () => {
|
||||
http.get.mockReturnValue(Promise.reject());
|
||||
wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first().simulate('click');
|
||||
await nextTick();
|
||||
|
||||
expect(flashAPIErrors).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { CurationsRouter } from './';
|
||||
|
||||
describe('CurationsRouter', () => {
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<CurationsRouter engineBreadcrumb={['Engines', 'some-engine']} />);
|
||||
|
||||
expect(wrapper.find(Switch)).toHaveLength(1);
|
||||
expect(wrapper.find(Route)).toHaveLength(5);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { APP_SEARCH_PLUGIN } from '../../../../../common/constants';
|
||||
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
|
||||
import { BreadcrumbTrail } from '../../../shared/kibana_chrome/generate_breadcrumbs';
|
||||
import { NotFound } from '../../../shared/not_found';
|
||||
import {
|
||||
ENGINE_CURATIONS_PATH,
|
||||
ENGINE_CURATIONS_NEW_PATH,
|
||||
ENGINE_CURATION_PATH,
|
||||
ENGINE_CURATION_ADD_RESULT_PATH,
|
||||
} from '../../routes';
|
||||
|
||||
import { CURATIONS_TITLE } from './constants';
|
||||
|
||||
interface Props {
|
||||
engineBreadcrumb: BreadcrumbTrail;
|
||||
}
|
||||
export const CurationsRouter: React.FC<Props> = ({ engineBreadcrumb }) => {
|
||||
const CURATIONS_BREADCRUMB = [...engineBreadcrumb, CURATIONS_TITLE];
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path={ENGINE_CURATIONS_PATH}>
|
||||
<SetPageChrome trail={CURATIONS_BREADCRUMB} />
|
||||
TODO: Curations overview
|
||||
</Route>
|
||||
<Route exact path={ENGINE_CURATIONS_NEW_PATH}>
|
||||
<SetPageChrome trail={[...CURATIONS_BREADCRUMB, 'Create a curation']} />
|
||||
TODO: Curation creation view
|
||||
</Route>
|
||||
<Route exact path={ENGINE_CURATION_PATH}>
|
||||
<SetPageChrome trail={[...CURATIONS_BREADCRUMB, 'curation queries']} />
|
||||
TODO: Curation view (+ show a NotFound view if ID is invalid)
|
||||
</Route>
|
||||
<Route exact path={ENGINE_CURATION_ADD_RESULT_PATH}>
|
||||
<SetPageChrome
|
||||
trail={[...CURATIONS_BREADCRUMB, 'curation queries', 'add result manually']}
|
||||
/>
|
||||
TODO: Curation Add Result view
|
||||
</Route>
|
||||
<Route>
|
||||
<NotFound breadcrumbs={CURATIONS_BREADCRUMB} product={APP_SEARCH_PLUGIN} />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
};
|
|
@ -6,3 +6,4 @@
|
|||
*/
|
||||
|
||||
export { CURATIONS_TITLE } from './constants';
|
||||
export { CurationsRouter } from './curations_router';
|
||||
|
|
|
@ -220,8 +220,8 @@ export const EngineNav: React.FC = () => {
|
|||
)}
|
||||
{canManageEngineCurations && (
|
||||
<SideNavLink
|
||||
isExternal
|
||||
to={getAppSearchUrl(generateEnginePath(ENGINE_CURATIONS_PATH))}
|
||||
to={generateEnginePath(ENGINE_CURATIONS_PATH)}
|
||||
shouldShowActiveForSubroutes
|
||||
data-test-subj="EngineCurationsLink"
|
||||
>
|
||||
{CURATIONS_TITLE}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { shallow } from 'enzyme';
|
|||
|
||||
import { Loading } from '../../../shared/loading';
|
||||
import { AnalyticsRouter } from '../analytics';
|
||||
import { CurationsRouter } from '../curations';
|
||||
import { EngineOverview } from '../engine_overview';
|
||||
import { RelevanceTuning } from '../relevance_tuning';
|
||||
|
||||
|
@ -97,7 +98,14 @@ describe('EngineRouter', () => {
|
|||
expect(wrapper.find(AnalyticsRouter)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders an relevance tuning view', () => {
|
||||
it('renders a curations view', () => {
|
||||
setMockValues({ ...values, myRole: { canManageEngineCurations: true } });
|
||||
const wrapper = shallow(<EngineRouter />);
|
||||
|
||||
expect(wrapper.find(CurationsRouter)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a relevance tuning view', () => {
|
||||
setMockValues({ ...values, myRole: { canManageEngineRelevanceTuning: true } });
|
||||
const wrapper = shallow(<EngineRouter />);
|
||||
|
||||
|
|
|
@ -28,12 +28,13 @@ import {
|
|||
// META_ENGINE_SOURCE_ENGINES_PATH,
|
||||
ENGINE_RELEVANCE_TUNING_PATH,
|
||||
// ENGINE_SYNONYMS_PATH,
|
||||
// ENGINE_CURATIONS_PATH,
|
||||
ENGINE_CURATIONS_PATH,
|
||||
// ENGINE_RESULT_SETTINGS_PATH,
|
||||
// ENGINE_SEARCH_UI_PATH,
|
||||
// ENGINE_API_LOGS_PATH,
|
||||
} from '../../routes';
|
||||
import { AnalyticsRouter } from '../analytics';
|
||||
import { CurationsRouter } from '../curations';
|
||||
import { DocumentDetail, Documents } from '../documents';
|
||||
import { OVERVIEW_TITLE } from '../engine_overview';
|
||||
import { EngineOverview } from '../engine_overview';
|
||||
|
@ -46,13 +47,13 @@ export const EngineRouter: React.FC = () => {
|
|||
const {
|
||||
myRole: {
|
||||
canViewEngineAnalytics,
|
||||
canManageEngineRelevanceTuning,
|
||||
// canViewEngineDocuments,
|
||||
// canViewEngineSchema,
|
||||
// canViewEngineCrawler,
|
||||
// canViewMetaEngineSourceEngines,
|
||||
canManageEngineRelevanceTuning,
|
||||
// canManageEngineSynonyms,
|
||||
// canManageEngineCurations,
|
||||
canManageEngineCurations,
|
||||
// canManageEngineResultSettings,
|
||||
// canManageEngineSearchUi,
|
||||
// canViewEngineApiLogs,
|
||||
|
@ -97,6 +98,11 @@ export const EngineRouter: React.FC = () => {
|
|||
<Route path={ENGINE_DOCUMENTS_PATH}>
|
||||
<Documents engineBreadcrumb={engineBreadcrumb} />
|
||||
</Route>
|
||||
{canManageEngineCurations && (
|
||||
<Route path={ENGINE_CURATIONS_PATH}>
|
||||
<CurationsRouter engineBreadcrumb={engineBreadcrumb} />
|
||||
</Route>
|
||||
)}
|
||||
{canManageEngineRelevanceTuning && (
|
||||
<Route path={ENGINE_RELEVANCE_TUNING_PATH}>
|
||||
<RelevanceTuning engineBreadcrumb={engineBreadcrumb} />
|
||||
|
|
|
@ -44,9 +44,12 @@ export const META_ENGINE_SOURCE_ENGINES_PATH = `${ENGINE_PATH}/engines`;
|
|||
|
||||
export const ENGINE_RELEVANCE_TUNING_PATH = `${ENGINE_PATH}/relevance_tuning`;
|
||||
export const ENGINE_SYNONYMS_PATH = `${ENGINE_PATH}/synonyms`;
|
||||
export const ENGINE_CURATIONS_PATH = `${ENGINE_PATH}/curations`;
|
||||
// TODO: Curations sub-pages
|
||||
export const ENGINE_RESULT_SETTINGS_PATH = `${ENGINE_PATH}/result-settings`;
|
||||
|
||||
export const ENGINE_CURATIONS_PATH = `${ENGINE_PATH}/curations`;
|
||||
export const ENGINE_CURATIONS_NEW_PATH = `${ENGINE_CURATIONS_PATH}/new`;
|
||||
export const ENGINE_CURATION_PATH = `${ENGINE_CURATIONS_PATH}/:curationId`;
|
||||
export const ENGINE_CURATION_ADD_RESULT_PATH = `${ENGINE_CURATIONS_PATH}/:curationId/add_result`;
|
||||
|
||||
export const ENGINE_SEARCH_UI_PATH = `${ENGINE_PATH}/reference_application/new`;
|
||||
export const ENGINE_API_LOGS_PATH = `${ENGINE_PATH}/api-logs`;
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__';
|
||||
|
||||
import { registerCurationsRoutes } from './curations';
|
||||
|
||||
describe('curations routes', () => {
|
||||
describe('GET /api/app_search/engines/{engineName}/curations/find_or_create', () => {
|
||||
let mockRouter: MockRouter;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockRouter = new MockRouter({
|
||||
method: 'get',
|
||||
path: '/api/app_search/engines/{engineName}/curations/find_or_create',
|
||||
});
|
||||
|
||||
registerCurationsRoutes({
|
||||
...mockDependencies,
|
||||
router: mockRouter.router,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a request handler', () => {
|
||||
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
|
||||
path: '/as/engines/:engineName/curations/find_or_create',
|
||||
});
|
||||
});
|
||||
|
||||
describe('validates', () => {
|
||||
it('required query param', () => {
|
||||
const request = { query: { query: 'some query' } };
|
||||
mockRouter.shouldValidate(request);
|
||||
});
|
||||
|
||||
it('missing query', () => {
|
||||
const request = { query: {} };
|
||||
mockRouter.shouldThrow(request);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { RouteDependencies } from '../../plugin';
|
||||
|
||||
export function registerCurationsRoutes({
|
||||
router,
|
||||
enterpriseSearchRequestHandler,
|
||||
}: RouteDependencies) {
|
||||
router.get(
|
||||
{
|
||||
path: '/api/app_search/engines/{engineName}/curations/find_or_create',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
engineName: schema.string(),
|
||||
}),
|
||||
query: schema.object({
|
||||
query: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
enterpriseSearchRequestHandler.createRequest({
|
||||
path: '/as/engines/:engineName/curations/find_or_create',
|
||||
})
|
||||
);
|
||||
}
|
|
@ -9,6 +9,7 @@ import { RouteDependencies } from '../../plugin';
|
|||
|
||||
import { registerAnalyticsRoutes } from './analytics';
|
||||
import { registerCredentialsRoutes } from './credentials';
|
||||
import { registerCurationsRoutes } from './curations';
|
||||
import { registerDocumentsRoutes, registerDocumentRoutes } from './documents';
|
||||
import { registerEnginesRoutes } from './engines';
|
||||
import { registerSearchSettingsRoutes } from './search_settings';
|
||||
|
@ -21,5 +22,6 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => {
|
|||
registerAnalyticsRoutes(dependencies);
|
||||
registerDocumentsRoutes(dependencies);
|
||||
registerDocumentRoutes(dependencies);
|
||||
registerCurationsRoutes(dependencies);
|
||||
registerSearchSettingsRoutes(dependencies);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue