diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.ts index c6fb83b172e5e..7452c2b7d1600 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.ts @@ -15,13 +15,19 @@ export interface FetchSyncJobsArgs { connectorId: string; from?: number; size?: number; + type?: 'content' | 'access_control'; } export type FetchSyncJobsResponse = Paginate; -export const fetchSyncJobs = async ({ connectorId, from = 0, size = 10 }: FetchSyncJobsArgs) => { +export const fetchSyncJobs = async ({ + connectorId, + from = 0, + size = 10, + type, +}: FetchSyncJobsArgs) => { const route = `/internal/enterprise_search/connectors/${connectorId}/sync_jobs`; - const query = { from, size }; + const query = { from, size, type }; return await HttpLogic.values.http.get>(route, { query }); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx index b82b555950380..b716804ff6dad 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx @@ -5,129 +5,67 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; -import { useActions, useValues } from 'kea'; +import { useValues } from 'kea'; -import { EuiBadge, EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; +import { EuiButtonGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SyncStatus } from '../../../../../../common/types/connectors'; - -import { FormattedDateTime } from '../../../../shared/formatted_date_time'; -import { pageToPagination } from '../../../../shared/pagination/page_to_pagination'; -import { durationToText } from '../../../utils/duration_to_text'; - -import { syncStatusToColor, syncStatusToText } from '../../../utils/sync_status_to_text'; +import { KibanaLogic } from '../../../../shared/kibana'; import { IndexViewLogic } from '../index_view_logic'; -import { SyncJobFlyout } from './sync_job_flyout'; -import { SyncJobsViewLogic, SyncJobView } from './sync_jobs_view_logic'; +import { SyncJobsHistoryTable } from './sync_jobs_history_table'; export const SyncJobs: React.FC = () => { - const { connectorId } = useValues(IndexViewLogic); - const { syncJobs, syncJobsLoading, syncJobsPagination } = useValues(SyncJobsViewLogic); - const { fetchSyncJobs } = useActions(SyncJobsViewLogic); - const [syncJobFlyout, setSyncJobFlyout] = useState(undefined); - - useEffect(() => { - if (connectorId) { - fetchSyncJobs({ - connectorId, - from: syncJobsPagination.from ?? 0, - size: syncJobsPagination.size ?? 10, - }); - } - }, [connectorId]); - - const columns: Array> = [ - { - field: 'lastSync', - name: i18n.translate('xpack.enterpriseSearch.content.syncJobs.lastSync.columnTitle', { - defaultMessage: 'Last sync', - }), - render: (lastSync: string) => , - sortable: true, - truncateText: true, - }, - { - field: 'duration', - name: i18n.translate('xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle', { - defaultMessage: 'Sync duration', - }), - render: (duration: moment.Duration) => durationToText(duration), - sortable: true, - truncateText: true, - }, - { - field: 'indexed_document_count', - name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.addedDocs.columnTitle', { - defaultMessage: 'Docs added', - }), - sortable: true, - truncateText: true, - }, - { - field: 'deleted_document_count', - name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.deletedDocs.columnTitle', { - defaultMessage: 'Docs deleted', - }), - sortable: true, - truncateText: true, - }, - { - field: 'status', - name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.syncStatus.columnTitle', { - defaultMessage: 'Status', - }), - render: (syncStatus: SyncStatus) => ( - {syncStatusToText(syncStatus)} - ), - truncateText: true, - }, - { - actions: [ - { - description: i18n.translate( - 'xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.title', - { - defaultMessage: 'View this sync job', - } - ), - icon: 'eye', - isPrimary: false, - name: i18n.translate( - 'xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.caption', - { - defaultMessage: 'View this sync job', - } - ), - onClick: (job) => setSyncJobFlyout(job), - type: 'icon', - }, - ], - }, - ]; + const { hasDocumentLevelSecurityFeature } = useValues(IndexViewLogic); + const { productFeatures } = useValues(KibanaLogic); + const [selectedSyncJobCategory, setSelectedSyncJobCategory] = useState('content'); + const shouldShowAccessSyncs = + productFeatures.hasDocumentLevelSecurityEnabled && hasDocumentLevelSecurityFeature; return ( <> - setSyncJobFlyout(undefined)} syncJob={syncJobFlyout} /> - { - if (connectorId) { - fetchSyncJobs({ connectorId, from: index * size, size }); - } - }} - pagination={pageToPagination(syncJobsPagination)} - tableLayout="fixed" - loading={syncJobsLoading} - /> + {shouldShowAccessSyncs && ( + { + setSelectedSyncJobCategory(optionId); + }} + options={[ + { + id: 'content', + label: i18n.translate( + 'xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.content.label', + { defaultMessage: 'Content syncs' } + ), + }, + + { + id: 'access_control', + label: i18n.translate( + 'xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.accessControl.label', + { defaultMessage: 'Access control syncs' } + ), + }, + ]} + /> + )} + {selectedSyncJobCategory === 'content' ? ( + + ) : ( + + )} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_history_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_history_table.tsx new file mode 100644 index 0000000000000..84fd9a30436d3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_history_table.tsx @@ -0,0 +1,181 @@ +/* + * 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, useState } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiBadge, EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { SyncJobType, SyncStatus } from '../../../../../../common/types/connectors'; +import { FormattedDateTime } from '../../../../shared/formatted_date_time'; +import { pageToPagination } from '../../../../shared/pagination/page_to_pagination'; + +import { durationToText } from '../../../utils/duration_to_text'; +import { + syncJobTypeToText, + syncStatusToColor, + syncStatusToText, +} from '../../../utils/sync_status_to_text'; + +import { IndexViewLogic } from '../index_view_logic'; + +import { SyncJobFlyout } from './sync_job_flyout'; +import { SyncJobsViewLogic, SyncJobView } from './sync_jobs_view_logic'; + +interface SyncJobHistoryTableProps { + type: 'content' | 'access_control'; +} + +export const SyncJobsHistoryTable: React.FC = ({ type }) => { + const { connectorId } = useValues(IndexViewLogic); + const { fetchSyncJobs } = useActions(SyncJobsViewLogic); + const { syncJobs, syncJobsLoading, syncJobsPagination } = useValues(SyncJobsViewLogic); + const [syncJobFlyout, setSyncJobFlyout] = useState(undefined); + + const columns: Array> = [ + { + field: 'lastSync', + name: i18n.translate('xpack.enterpriseSearch.content.syncJobs.lastSync.columnTitle', { + defaultMessage: 'Last sync', + }), + render: (lastSync: string) => , + sortable: true, + truncateText: true, + }, + { + field: 'duration', + name: i18n.translate('xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle', { + defaultMessage: 'Sync duration', + }), + render: (duration: moment.Duration) => durationToText(duration), + sortable: true, + truncateText: true, + }, + ...(type === 'content' + ? [ + { + field: 'indexed_document_count', + name: i18n.translate( + 'xpack.enterpriseSearch.content.searchIndices.addedDocs.columnTitle', + { + defaultMessage: 'Docs added', + } + ), + sortable: true, + truncateText: true, + }, + { + field: 'deleted_document_count', + name: i18n.translate( + 'xpack.enterpriseSearch.content.searchIndices.deletedDocs.columnTitle', + { + defaultMessage: 'Docs deleted', + } + ), + sortable: true, + truncateText: true, + }, + { + field: 'job_type', + name: i18n.translate( + 'xpack.enterpriseSearch.content.searchIndices.syncJobType.columnTitle', + { + defaultMessage: 'Content sync type', + } + ), + render: (syncType: SyncJobType) => { + const syncJobTypeText = syncJobTypeToText(syncType); + if (syncJobTypeText.length === 0) return null; + return {syncJobTypeText}; + }, + sortable: true, + truncateText: true, + }, + ] + : []), + ...(type === 'access_control' + ? [ + { + field: 'indexed_document_count', + name: i18n.translate( + 'xpack.enterpriseSearch.content.searchIndices.identitySync.columnTitle', + { + defaultMessage: 'Identities synced', + } + ), + sortable: true, + truncateText: true, + }, + ] + : []), + { + field: 'status', + name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.syncStatus.columnTitle', { + defaultMessage: 'Status', + }), + render: (syncStatus: SyncStatus) => ( + {syncStatusToText(syncStatus)} + ), + truncateText: true, + }, + { + actions: [ + { + description: i18n.translate( + 'xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.title', + { + defaultMessage: 'View this sync job', + } + ), + icon: 'eye', + isPrimary: false, + name: i18n.translate( + 'xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.caption', + { + defaultMessage: 'View this sync job', + } + ), + onClick: (job) => setSyncJobFlyout(job), + type: 'icon', + }, + ], + }, + ]; + + useEffect(() => { + if (connectorId) { + fetchSyncJobs({ + connectorId, + from: syncJobsPagination.from ?? 0, + size: syncJobsPagination.size ?? 10, + type, + }); + } + }, [connectorId, type]); + return ( + <> + setSyncJobFlyout(undefined)} syncJob={syncJobFlyout} /> + { + if (connectorId) { + fetchSyncJobs({ connectorId, from: index * size, size, type }); + } + }} + pagination={pageToPagination(syncJobsPagination)} + tableLayout="fixed" + loading={syncJobsLoading} + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/sync_status_to_text.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/sync_status_to_text.ts index 5d79f92565a34..6868fba40e59f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/sync_status_to_text.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/sync_status_to_text.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; -import { SyncStatus } from '../../../../common/types/connectors'; +import { SyncJobType, SyncStatus } from '../../../../common/types/connectors'; export function syncStatusToText(status: SyncStatus): string { switch (status) { @@ -56,3 +56,18 @@ export function syncStatusToColor(status: SyncStatus): string { return 'warning'; } } + +export const syncJobTypeToText = (syncType: SyncJobType): string => { + switch (syncType) { + case SyncJobType.FULL: + return i18n.translate('xpack.enterpriseSearch.content.syncJobType.full', { + defaultMessage: 'Full content', + }); + case SyncJobType.INCREMENTAL: + return i18n.translate('xpack.enterpriseSearch.content.syncJobType.incremental', { + defaultMessage: 'Incremental content', + }); + default: + return ''; + } +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts index 25369fa7979bb..23d9bcc842d18 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts @@ -8,7 +8,7 @@ import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { CONNECTORS_JOBS_INDEX } from '../..'; -import { ConnectorSyncJob } from '../../../common/types/connectors'; +import { ConnectorSyncJob, SyncJobType } from '../../../common/types/connectors'; import { Paginate } from '../../../common/types/pagination'; import { isNotNullish } from '../../../common/utils/is_not_nullish'; @@ -32,19 +32,42 @@ export const fetchSyncJobsByConnectorId = async ( client: IScopedClusterClient, connectorId: string, from: number, - size: number + size: number, + syncJobType: 'content' | 'access_control' | 'all' = 'all' ): Promise> => { try { + const query = + syncJobType === 'all' + ? { + term: { + 'connector.id': connectorId, + }, + } + : { + bool: { + filter: [ + { + term: { + 'connector.id': connectorId, + }, + }, + { + terms: { + job_type: + syncJobType === 'content' + ? [SyncJobType.FULL, SyncJobType.INCREMENTAL] + : [SyncJobType.ACCESS_CONTROL], + }, + }, + ], + }, + }; const result = await fetchWithPagination( async () => await client.asCurrentUser.search({ from, index: CONNECTORS_JOBS_INDEX, - query: { - term: { - 'connector.id': connectorId, - }, - }, + query, size, sort: { created_at: { order: 'desc' } }, }), diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 40b9231b16ccd..1cac51b7456cd 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -208,6 +208,7 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { query: schema.object({ from: schema.number({ defaultValue: 0, min: 0 }), size: schema.number({ defaultValue: 10, min: 0 }), + type: schema.maybe(schema.string()), }), }, }, @@ -217,7 +218,8 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { client, request.params.connectorId, request.query.from, - request.query.size + request.query.size, + request.query.type as 'content' | 'access_control' | 'all' ); return response.ok({ body: result }); }) diff --git a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts index 965c41676a6ce..27a7ad5d9340c 100644 --- a/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts +++ b/x-pack/plugins/enterprise_search/server/utils/create_connector_document.ts @@ -127,8 +127,8 @@ export function createConnectorDocument({ pipeline, scheduling: { access_control: { enabled: false, interval: '0 0 0 * * ?' }, - incremental: { enabled: false, interval: '0 0 0 * * ?' }, full: { enabled: false, interval: '0 0 0 * * ?' }, + incremental: { enabled: false, interval: '0 0 0 * * ?' }, }, service_type: serviceType || null, status: ConnectorStatus.CREATED, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 6b6f049f79bc1..3c991164dbe7a 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -39672,4 +39672,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "Présentation" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 314f187038e2d..805d601396840 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -39642,4 +39642,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "実地検証" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3586f89017cac..d317e9a7accb1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -39636,4 +39636,4 @@ "xpack.painlessLab.title": "Painless 实验室", "xpack.painlessLab.walkthroughButtonLabel": "指导" } -} \ No newline at end of file +}