Skip to content

Commit

Permalink
[Search] Connector detail page (elastic#176158)
Browse files Browse the repository at this point in the history
## Summary

New Connector detail page.


<img width="1367" alt="Screenshot 2024-02-02 at 17 50 46"
src="https://github.com/elastic/kibana/assets/1410658/64abf292-6ff6-42c9-8ccb-4e3300a6c16a">




Look and feel is not polished yet but functionality works.


### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
efegurkan and kibanamachine authored Feb 7, 2024
1 parent c8e6816 commit 9df8724
Show file tree
Hide file tree
Showing 11 changed files with 933 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 { Connector } from '@kbn/search-connectors';

import { createApiLogic, Actions } from '../../../shared/api_logic/create_api_logic';
import { HttpLogic } from '../../../shared/http';

export interface FetchConnectorByIdApiLogicArgs {
connectorId: string;
}
export interface FetchConnectorByIdApiLogicResponse {
connector: Connector | undefined;
}

export const fetchConnectorById = async ({
connectorId,
}: FetchConnectorByIdApiLogicArgs): Promise<FetchConnectorByIdApiLogicResponse> => {
const route = `/internal/enterprise_search/connectors/${connectorId}`;
const response = await HttpLogic.values.http.get<FetchConnectorByIdApiLogicResponse>(route);
return response;
};

export const FetchConnectorByIdApiLogic = createApiLogic(
['fetch_connector_by_id_api_logic'],
fetchConnectorById
);

export type FetchConnectorByIdApiLogicActions = Actions<
FetchConnectorByIdApiLogicArgs,
FetchConnectorByIdApiLogicResponse
>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
* 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, { useEffect } from 'react';
import { useParams } from 'react-router-dom';

import { useActions, useValues } from 'kea';

import { i18n } from '@kbn/i18n';

import { generateEncodedPath } from '../../../shared/encode_path_params';
import { KibanaLogic } from '../../../shared/kibana';
import { CONNECTOR_DETAIL_TAB_PATH } from '../../routes';
import { baseBreadcrumbs } from '../connectors/connectors';
import { EnterpriseSearchContentPageTemplate } from '../layout/page_template';

import { getHeaderActions } from '../search_index/components/header_actions/header_actions';
import { ConnectorConfiguration } from '../search_index/connector/connector_configuration';
import { ConnectorSchedulingComponent } from '../search_index/connector/connector_scheduling';
import { ConnectorSyncRules } from '../search_index/connector/sync_rules/connector_rules';
import { SearchIndexDocuments } from '../search_index/documents';
import { SearchIndexIndexMappings } from '../search_index/index_mappings';
import { SearchIndexPipelines } from '../search_index/pipelines/pipelines';

import { ConnectorViewLogic } from './connector_view_logic';
import { ConnectorDetailOverview } from './overview';

export enum ConnectorDetailTabId {
// all indices
OVERVIEW = 'overview',
DOCUMENTS = 'documents',
INDEX_MAPPINGS = 'index_mappings',
PIPELINES = 'pipelines',
// connector indices
CONFIGURATION = 'configuration',
SYNC_RULES = 'sync_rules',
SCHEDULING = 'scheduling',
}

export const ConnectorDetail: React.FC = () => {
const connectorId = decodeURIComponent(useParams<{ connectorId: string }>().connectorId);
const { hasFilteringFeature, isLoading, index, connector } = useValues(ConnectorViewLogic);
const { fetchConnector } = useActions(ConnectorViewLogic);
useEffect(() => {
fetchConnector({ connectorId });
}, []);

const { tabId = ConnectorDetailTabId.OVERVIEW } = useParams<{
tabId?: string;
}>();

const {
productAccess: { hasAppSearchAccess },
productFeatures: { hasDefaultIngestPipeline },
} = useValues(KibanaLogic);

const ALL_INDICES_TABS = [
{
content: <ConnectorDetailOverview />,
id: ConnectorDetailTabId.OVERVIEW,
isSelected: tabId === ConnectorDetailTabId.OVERVIEW,
label: i18n.translate(
'xpack.enterpriseSearch.content.connectors.connectorDetail.overviewTabLabel',
{
defaultMessage: 'Overview',
}
),
onClick: () =>
KibanaLogic.values.navigateToUrl(
generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
connectorId,
tabId: ConnectorDetailTabId.OVERVIEW,
})
),
},
{
content: <SearchIndexDocuments />,
disabled: !index,
id: ConnectorDetailTabId.DOCUMENTS,
isSelected: tabId === ConnectorDetailTabId.DOCUMENTS,
label: i18n.translate(
'xpack.enterpriseSearch.content.connectors.connectorDetail.documentsTabLabel',
{
defaultMessage: 'Documents',
}
),
onClick: () =>
KibanaLogic.values.navigateToUrl(
generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
connectorId,
tabId: ConnectorDetailTabId.DOCUMENTS,
})
),
},
{
content: <SearchIndexIndexMappings />,
disabled: !index,
id: ConnectorDetailTabId.INDEX_MAPPINGS,
isSelected: tabId === ConnectorDetailTabId.INDEX_MAPPINGS,
label: i18n.translate(
'xpack.enterpriseSearch.content.connectors.connectorDetail.indexMappingsTabLabel',
{
defaultMessage: 'Index mappings',
}
),
onClick: () =>
KibanaLogic.values.navigateToUrl(
generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
connectorId,
tabId: ConnectorDetailTabId.INDEX_MAPPINGS,
})
),
},
];

const CONNECTOR_TABS = [
{
content: <ConnectorConfiguration />,
id: ConnectorDetailTabId.CONFIGURATION,
isSelected: tabId === ConnectorDetailTabId.CONFIGURATION,
label: i18n.translate(
'xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel',
{
defaultMessage: 'Configuration',
}
),
onClick: () =>
KibanaLogic.values.navigateToUrl(
generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
connectorId,
tabId: ConnectorDetailTabId.CONFIGURATION,
})
),
},
...(hasFilteringFeature
? [
{
content: <ConnectorSyncRules />,
disabled: !index,
id: ConnectorDetailTabId.SYNC_RULES,
isSelected: tabId === ConnectorDetailTabId.SYNC_RULES,
label: i18n.translate(
'xpack.enterpriseSearch.content.connectors.connectorDetail.syncRulesTabLabel',
{
defaultMessage: 'Sync rules',
}
),
onClick: () =>
KibanaLogic.values.navigateToUrl(
generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
connectorId,
tabId: ConnectorDetailTabId.SYNC_RULES,
})
),
},
]
: []),
{
content: <ConnectorSchedulingComponent />,
disabled: !index,
id: ConnectorDetailTabId.SCHEDULING,
isSelected: tabId === ConnectorDetailTabId.SCHEDULING,
label: i18n.translate(
'xpack.enterpriseSearch.content.connectors.connectorDetail.schedulingTabLabel',
{
defaultMessage: 'Scheduling',
}
),
onClick: () =>
KibanaLogic.values.navigateToUrl(
generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
connectorId,
tabId: ConnectorDetailTabId.SCHEDULING,
})
),
},
];

const PIPELINES_TAB = {
content: <SearchIndexPipelines />,
disabled: !index,
id: ConnectorDetailTabId.PIPELINES,
isSelected: tabId === ConnectorDetailTabId.PIPELINES,
label: i18n.translate(
'xpack.enterpriseSearch.content.connectors.connectorDetail.pipelinesTabLabel',
{
defaultMessage: 'Pipelines',
}
),
onClick: () =>
KibanaLogic.values.navigateToUrl(
generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
connectorId,
tabId: ConnectorDetailTabId.PIPELINES,
})
),
};

interface TabMenuItem {
content: JSX.Element;
disabled?: boolean;
id: string;
label: string;
onClick?: () => void;
prepend?: React.ReactNode;
route?: string;
testSubj?: string;
}

const tabs: TabMenuItem[] = [
...ALL_INDICES_TABS,
...CONNECTOR_TABS,
...(hasDefaultIngestPipeline ? [PIPELINES_TAB] : []),
];

const selectedTab = tabs.find((tab) => tab.id === tabId);

return (
<EnterpriseSearchContentPageTemplate
pageChrome={[...baseBreadcrumbs, connector?.name ?? '...']}
pageViewTelemetry={tabId}
isLoading={isLoading}
pageHeader={{
pageTitle: connector?.name ?? '...',
rightSideItems: getHeaderActions(index, hasAppSearchAccess),
tabs,
}}
>
{selectedTab?.content || null}
</EnterpriseSearchContentPageTemplate>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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, { useEffect } from 'react';

import { useActions } from 'kea';

import { Routes, Route } from '@kbn/shared-ux-router';

import { CONNECTOR_DETAIL_PATH, CONNECTOR_DETAIL_TAB_PATH } from '../../routes';

import { IndexNameLogic } from '../search_index/index_name_logic';

import { IndexViewLogic } from '../search_index/index_view_logic';

import { ConnectorDetail } from './connector_detail';
import { ConnectorViewLogic } from './connector_view_logic';

export const ConnectorDetailRouter: React.FC = () => {
const { stopFetchIndexPoll } = useActions(IndexViewLogic);
useEffect(() => {
const unmountName = IndexNameLogic.mount();
const unmountView = ConnectorViewLogic.mount();
const unmountIndexView = IndexViewLogic.mount();
return () => {
stopFetchIndexPoll();
unmountName();
unmountView();
unmountIndexView();
};
}, []);
return (
<Routes>
<Route path={CONNECTOR_DETAIL_PATH} exact>
<ConnectorDetail />
</Route>
<Route path={CONNECTOR_DETAIL_TAB_PATH}>
<ConnectorDetail />
</Route>
</Routes>
);
};
Loading

0 comments on commit 9df8724

Please sign in to comment.