Skip to content

Commit

Permalink
[Enterprise Search] [Document Level Security] Access Control sync his…
Browse files Browse the repository at this point in the history
…tory (elastic#159461)

## Summary

- Adds Access Control sync to the index overview page.
- Adds table switcher and changes table columns.

Content related syncs
![Screenshot 2023-06-12 at 14 49
10](https://github.com/elastic/kibana/assets/1410658/2d4d5c44-2648-4d1d-ba86-aff38aae17fb)

Access control syncs
![Screenshot 2023-06-12 at 14 49
14](https://github.com/elastic/kibana/assets/1410658/0ab11462-cecb-4099-955d-4f2f6d02197a)

When access control not enabled
![Screenshot 2023-06-12 at 14 54
11](https://github.com/elastic/kibana/assets/1410658/e93430a4-d02a-46dc-b212-1ec1158fb7b8)


### 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
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] 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 Jun 12, 2023
1 parent e8e5cec commit ebcfebe
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ export interface FetchSyncJobsArgs {
connectorId: string;
from?: number;
size?: number;
type?: 'content' | 'access_control';
}

export type FetchSyncJobsResponse = Paginate<ConnectorSyncJob>;

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<Paginate<ConnectorSyncJob>>(route, { query });
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SyncJobView | undefined>(undefined);

useEffect(() => {
if (connectorId) {
fetchSyncJobs({
connectorId,
from: syncJobsPagination.from ?? 0,
size: syncJobsPagination.size ?? 10,
});
}
}, [connectorId]);

const columns: Array<EuiBasicTableColumn<SyncJobView>> = [
{
field: 'lastSync',
name: i18n.translate('xpack.enterpriseSearch.content.syncJobs.lastSync.columnTitle', {
defaultMessage: 'Last sync',
}),
render: (lastSync: string) => <FormattedDateTime date={new Date(lastSync)} />,
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) => (
<EuiBadge color={syncStatusToColor(syncStatus)}>{syncStatusToText(syncStatus)}</EuiBadge>
),
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<string>('content');
const shouldShowAccessSyncs =
productFeatures.hasDocumentLevelSecurityEnabled && hasDocumentLevelSecurityFeature;

return (
<>
<SyncJobFlyout onClose={() => setSyncJobFlyout(undefined)} syncJob={syncJobFlyout} />
<EuiBasicTable
data-test-subj="entSearchContent-index-syncJobs-table"
items={syncJobs}
columns={columns}
hasActions
onChange={({ page: { index, size } }: { page: { index: number; size: number } }) => {
if (connectorId) {
fetchSyncJobs({ connectorId, from: index * size, size });
}
}}
pagination={pageToPagination(syncJobsPagination)}
tableLayout="fixed"
loading={syncJobsLoading}
/>
{shouldShowAccessSyncs && (
<EuiButtonGroup
legend={i18n.translate(
'xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend',
{ defaultMessage: 'Select sync job type to display.' }
)}
name={i18n.translate(
'xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name',
{ defaultMessage: 'Sync job type' }
)}
idSelected={selectedSyncJobCategory}
onChange={(optionId) => {
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' ? (
<SyncJobsHistoryTable type="content" />
) : (
<SyncJobsHistoryTable type="access_control" />
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -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<SyncJobHistoryTableProps> = ({ type }) => {
const { connectorId } = useValues(IndexViewLogic);
const { fetchSyncJobs } = useActions(SyncJobsViewLogic);
const { syncJobs, syncJobsLoading, syncJobsPagination } = useValues(SyncJobsViewLogic);
const [syncJobFlyout, setSyncJobFlyout] = useState<SyncJobView | undefined>(undefined);

const columns: Array<EuiBasicTableColumn<SyncJobView>> = [
{
field: 'lastSync',
name: i18n.translate('xpack.enterpriseSearch.content.syncJobs.lastSync.columnTitle', {
defaultMessage: 'Last sync',
}),
render: (lastSync: string) => <FormattedDateTime date={new Date(lastSync)} />,
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 <EuiBadge color="hollow">{syncJobTypeText}</EuiBadge>;
},
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) => (
<EuiBadge color={syncStatusToColor(syncStatus)}>{syncStatusToText(syncStatus)}</EuiBadge>
),
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 (
<>
<SyncJobFlyout onClose={() => setSyncJobFlyout(undefined)} syncJob={syncJobFlyout} />
<EuiBasicTable
data-test-subj={`entSearchContent-index-${type}-syncJobs-table`}
items={syncJobs}
columns={columns}
hasActions
onChange={({ page: { index, size } }: { page: { index: number; size: number } }) => {
if (connectorId) {
fetchSyncJobs({ connectorId, from: index * size, size, type });
}
}}
pagination={pageToPagination(syncJobsPagination)}
tableLayout="fixed"
loading={syncJobsLoading}
/>
</>
);
};
Loading

0 comments on commit ebcfebe

Please sign in to comment.