Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Workplace Search] Migrate Sources Schema tree #84847

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export const ADD = 'add';
export const UPDATE = 'update';
export const REMOVE = 'remove';
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { ADD, UPDATE } from './constants/operations';

export type SchemaTypes = 'text' | 'number' | 'geolocation' | 'date';

export interface Schema {
Expand Down Expand Up @@ -32,3 +34,10 @@ export interface IIndexingStatus {
numDocumentsWithErrors: number;
activeReindexJobId: number;
}

export interface IndexJob extends IIndexingStatus {
isActive?: boolean;
hasErrors?: boolean;
}

export type TOperation = typeof ADD | typeof UPDATE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove T in the type name?
If yes, feel free to merge and fix in later PRs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Will address this in a follow-up PR.

Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,9 @@ export const getGroupSourcePrioritizationPath = (groupId: string): string =>
`${GROUPS_PATH}/${groupId}/source_prioritization`;
export const getSourcesPath = (path: string, isOrganization: boolean): string =>
isOrganization ? path : `${PERSONAL_PATH}${path}`;
export const getReindexJobRoute = (
sourceId: string,
activeReindexJobId: string,
isOrganization: boolean
) =>
getSourcesPath(generatePath(REINDEX_JOB_PATH, { sourceId, activeReindexJobId }), isOrganization);
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';

export const SCHEMA_ERRORS_HEADING = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.heading',
{
defaultMessage: 'Schema Change Errors',
}
);

export const SCHEMA_ERRORS_TABLE_FIELD_NAME_HEADER = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.header.fieldName',
{
defaultMessage: 'Field Name',
}
);

export const SCHEMA_ERRORS_TABLE_DATA_TYPE_HEADER = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.header.dataType',
{
defaultMessage: 'Data Type',
}
);

export const SCHEMA_FIELD_ERRORS_ERROR_MESSAGE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.message',
{
defaultMessage: 'Oops, we were not able to find any errors for this Schema.',
}
);

export const SCHEMA_FIELD_ADDED_MESSAGE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.fieldAdded.message',
{
defaultMessage: 'New field added.',
}
);

export const SCHEMA_UPDATED_MESSAGE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.updated.message',
{
defaultMessage: 'Schema updated.',
}
);

export const SCHEMA_ADD_FIELD_BUTTON = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.addField.button',
{
defaultMessage: 'Add field',
}
);

export const SCHEMA_MANAGE_SCHEMA_TITLE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.manage.title',
{
defaultMessage: 'Manage source schema',
}
);

export const SCHEMA_MANAGE_SCHEMA_DESCRIPTION = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.manage.description',
{
defaultMessage: 'Add new fields or change the types of existing ones',
}
);

export const SCHEMA_FILTER_PLACEHOLDER = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.filter.placeholder',
{
defaultMessage: 'Filter schema fields...',
}
);

export const SCHEMA_UPDATING = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.updating',
{
defaultMessage: 'Updating schema...',
}
);

export const SCHEMA_SAVE_BUTTON = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.save.button',
{
defaultMessage: 'Save schema',
}
);

export const SCHEMA_EMPTY_SCHEMA_TITLE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.empty.title',
{
defaultMessage: 'Content source does not have a schema',
}
);

export const SCHEMA_EMPTY_SCHEMA_DESCRIPTION = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.empty.description',
{
defaultMessage:
'A schema is created for you once you index some documents. Click below to create schema fields in advance.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,161 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import React, { useEffect } from 'react';

export const Schema: React.FC = () => <>Schema Placeholder</>;
import { useActions, useValues } from 'kea';

import {
EuiButton,
EuiButtonEmpty,
EuiEmptyPrompt,
EuiFieldSearch,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiPanel,
} from '@elastic/eui';

import { getReindexJobRoute } from '../../../../routes';
import { AppLogic } from '../../../../app_logic';

import { Loading } from '../../../../../shared/loading';
import { ViewContentHeader } from '../../../../components/shared/view_content_header';

import { SchemaAddFieldModal } from '../../../../../shared/schema/schema_add_field_modal';
import { IndexingStatus } from '../../../../../shared/indexing_status';

import { SchemaFieldsTable } from './schema_fields_table';
import { SchemaLogic } from './schema_logic';

import {
SCHEMA_ADD_FIELD_BUTTON,
SCHEMA_MANAGE_SCHEMA_TITLE,
SCHEMA_MANAGE_SCHEMA_DESCRIPTION,
SCHEMA_FILTER_PLACEHOLDER,
SCHEMA_UPDATING,
SCHEMA_SAVE_BUTTON,
SCHEMA_EMPTY_SCHEMA_TITLE,
SCHEMA_EMPTY_SCHEMA_DESCRIPTION,
} from './constants';

export const Schema: React.FC = () => {
const {
initializeSchema,
onIndexingComplete,
addNewField,
updateFields,
openAddFieldModal,
closeAddFieldModal,
setFilterValue,
} = useActions(SchemaLogic);

const {
sourceId,
activeSchema,
filterValue,
showAddFieldModal,
addFieldFormErrors,
mostRecentIndexJob,
formUnchanged,
dataLoading,
} = useValues(SchemaLogic);

const { isOrganization } = useValues(AppLogic);

useEffect(() => {
initializeSchema();
}, []);

if (dataLoading) return <Loading />;

const hasSchemaFields = Object.keys(activeSchema).length > 0;
const { isActive, hasErrors, percentageComplete, activeReindexJobId } = mostRecentIndexJob;

const addFieldButton = (
<EuiButtonEmpty color="primary" data-test-subj="AddFieldButton" onClick={openAddFieldModal}>
{SCHEMA_ADD_FIELD_BUTTON}
</EuiButtonEmpty>
);
const statusPath = isOrganization
? `/api/workplace_search/org/sources/${sourceId}/reindex_job/${activeReindexJobId}/status`
: `/api/workplace_search/account/sources/${sourceId}/reindex_job/${activeReindexJobId}/status`;

return (
<>
<ViewContentHeader
title={SCHEMA_MANAGE_SCHEMA_TITLE}
description={SCHEMA_MANAGE_SCHEMA_DESCRIPTION}
/>
<div>
{(isActive || hasErrors) && (
<IndexingStatus
itemId={sourceId}
viewLinkPath={getReindexJobRoute(
sourceId,
mostRecentIndexJob.activeReindexJobId.toString(),
isOrganization
)}
statusPath={statusPath}
onComplete={onIndexingComplete}
{...mostRecentIndexJob}
/>
)}
{hasSchemaFields ? (
<>
<EuiSpacer />
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem>
<EuiFieldSearch
value={filterValue}
data-test-subj="FilterSchemaInput"
placeholder={SCHEMA_FILTER_PLACEHOLDER}
onChange={(e) => setFilterValue(e.target.value)}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem>{addFieldButton}</EuiFlexItem>
<EuiFlexItem grow={false}>
{percentageComplete < 100 ? (
<EuiButton isLoading={true} fill={true}>
{SCHEMA_UPDATING}
</EuiButton>
) : (
<EuiButton
disabled={formUnchanged}
data-test-subj="UpdateTypesButton"
onClick={updateFields}
fill={true}
>
{SCHEMA_SAVE_BUTTON}
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<SchemaFieldsTable />
</>
) : (
<EuiPanel className="euiPanel--inset">
<EuiEmptyPrompt
iconType="managementApp"
title={<h2>{SCHEMA_EMPTY_SCHEMA_TITLE}</h2>}
body={<p>{SCHEMA_EMPTY_SCHEMA_DESCRIPTION}</p>}
actions={addFieldButton}
/>
</EuiPanel>
)}
</div>
{showAddFieldModal && (
<SchemaAddFieldModal
addFieldFormErrors={addFieldFormErrors}
addNewField={addNewField}
closeAddFieldModal={closeAddFieldModal}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,42 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';

export const SchemaChangeErrors: React.FC = () => <>Schema Errors Placeholder</>;
import { useActions, useValues } from 'kea';

import { EuiSpacer } from '@elastic/eui';

import { SchemaErrorsAccordion } from '../../../../../shared/schema/schema_errors_accordion';
import { ViewContentHeader } from '../../../../components/shared/view_content_header';
import { SchemaLogic } from './schema_logic';
import { SCHEMA_ERRORS_HEADING } from './constants';

export const SchemaChangeErrors: React.FC = () => {
const { activeReindexJobId, sourceId } = useParams() as {
activeReindexJobId: string;
sourceId: string;
};
const { initializeSchemaFieldErrors } = useActions(SchemaLogic);

const { fieldCoercionErrors, serverSchema } = useValues(SchemaLogic);

useEffect(() => {
initializeSchemaFieldErrors(activeReindexJobId, sourceId);
}, []);

return (
<div>
<ViewContentHeader title={SCHEMA_ERRORS_HEADING} />
<EuiSpacer size="xl" />
<main>
<SchemaErrorsAccordion
fieldCoercionErrors={fieldCoercionErrors}
schema={serverSchema}
itemId={sourceId}
/>
</main>
</div>
);
};
Loading