Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 All @@ -27,8 +29,16 @@ export interface SchemaConflicts {
[key: string]: SchemaConflictFieldTypes;
}


export interface IIndexingStatus {
percentageComplete: number;
numDocumentsWithErrors: number;
activeReindexJobId: number;
}

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

export type TOperation = typeof ADD | typeof UPDATE;

Copy link
Copy Markdown
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
Copy Markdown
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
Expand Up @@ -4,6 +4,153 @@
* 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 { AppLogic } from 'workplace_search/App/AppLogic';
import { Loading, ViewContentHeader } from 'workplace_search/components';
import { getReindexJobRoute } from '../../../../routes';

import IndexingStatus from 'shared/components/IndexingStatus';
import { SchemaAddFieldModal } from 'shared/components/Schema';

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

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}>
Add Field
</EuiButtonEmpty>
);
const statusPath = isOrganization
? `/api/workplace_search/org/sources/${sourceId}/reindex_job/${activeReindexJobId}`
: `/api/workplace_search/account/sources/${sourceId}/reindex_job/${activeReindexJobId}`;

return (
<>
<ViewContentHeader
title="Manage source schema"
description="Add new fields or change the types of existing ones"
/>
<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="Filter schema fields..."
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}>
Updating schema...
</EuiButton>
) : (
<EuiButton
disabled={formUnchanged}
data-test-subj="UpdateTypesButton"
onClick={updateFields}
fill={true}
>
Save Schema
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<SchemaFieldsTable />
</>
) : (
<EuiPanel className="euiPanel--inset">
<EuiEmptyPrompt
iconType="managementApp"
title={<h2>Content source does not have a schema</h2>}
body={
<p>
A schema is created for you once you index some documents. Click below to create
schema fields in advance.
</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,41 @@
* 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/components/Schema/SchemaErrorsAccordion';
import { ViewContentHeader } from 'workplace_search/components';
import { SchemaLogic } from './SchemaLogic';

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 Change Errors" />
<EuiSpacer size="xl" />
<main>
<SchemaErrorsAccordion
fieldCoercionErrors={fieldCoercionErrors}
schema={serverSchema}
itemId={sourceId}
/>
</main>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 React from 'react';

import { useActions, useValues } from 'kea';

import {
EuiFlexGroup,
EuiFlexItem,
EuiTable,
EuiTableBody,
EuiTableHeader,
EuiTableHeaderCell,
EuiTableRow,
EuiTableRowCell,
} from '@elastic/eui';

import { SchemaExistingField } from 'shared/components/Schema';
import { SchemaLogic } from './SchemaLogic';

export const SchemaFieldsTable: React.FC = () => {
const { updateExistingFieldType } = useActions(SchemaLogic);

const { filteredSchemaFields, filterValue } = useValues(SchemaLogic);

return Object.keys(filteredSchemaFields).length > 0 ? (
<EuiTable>
<EuiTableHeader>
<EuiTableHeaderCell>Field Name</EuiTableHeaderCell>
<EuiTableHeaderCell>Data Type</EuiTableHeaderCell>
</EuiTableHeader>
<EuiTableBody>
{Object.keys(filteredSchemaFields).map((fieldName) => (
<EuiTableRow key={fieldName} data-test-subj="SchemaFieldRow">
<EuiTableRowCell>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem>
<strong>{fieldName}</strong>
</EuiFlexItem>
</EuiFlexGroup>
</EuiTableRowCell>
<EuiTableRowCell>
<SchemaExistingField
disabled={fieldName === 'id'}
key={fieldName}
fieldName={fieldName}
hideName={true}
fieldType={filteredSchemaFields[fieldName]}
updateExistingFieldType={updateExistingFieldType}
/>
</EuiTableRowCell>
</EuiTableRow>
))}
</EuiTableBody>
</EuiTable>
) : (
<p>No results found for &apos;{filterValue}&apos;.</p>
);
};
Loading