Skip to content

Commit

Permalink
[Workplace Search] Migrate Sources Schema tree (#84847)
Browse files Browse the repository at this point in the history
* Initial copy/paste of component tree

Only does linting changes and:
- lodash imports
- Replace unescaped apostrophes with '
- Fix ternary function call to if block:

 if (isAdding) {
  actions.onSchemaSetFormErrors(errors);
} else {
  actions.onSchemaSetError({ flashMessages: { error: errors } });
}

* Remove local flash messages from component

* Update paths

- Adds getReindexJobRoute method to routes
- Repalces legacy Rails routes helper with hard-coded paths

* Add types and constants

* Update paths

* Replace local flash message logic with gobal

* Update with newly added types

Added here: #84822

* Update server routes

* Replace Rails http with kibana http

* Set percentage to 0 when updating

Without this, the IndexingStatus never shows.

* Fix route paths

* Fix server route validation

The empty object was breaking the UI since `schema.object({})` is actually an empty object. This is more explicit and correct.

* Add i18n

* Make sure i18n key is unique

* Lint
  • Loading branch information
scottybollinger authored Dec 3, 2020
1 parent c39d14f commit 235f786
Show file tree
Hide file tree
Showing 9 changed files with 770 additions and 6 deletions.
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;
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

0 comments on commit 235f786

Please sign in to comment.