Skip to content

Commit

Permalink
[App Search] Schema view (elastic#99868)
Browse files Browse the repository at this point in the history
* Add schema add field modal to page

* Add SchemaCallouts component

* Add empty state

* Add SchemaTable component

- Main update functionality is covered by the shared SchemaFieldTypeSelect component

+ Readd 'Recently added' i18n strings removed in https://github.com/elastic/kibana/pull/98955/files/d93e31dd415ba61354ab3714cd2728d60efa6ddc#r624038044

squash with table

* Add final update types button behavior

+ expand tests more explicitly & cleanly

* Fix i18n

just throw my body in the trash

* Update x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx

Co-authored-by: Jason Stoltzfus <[email protected]>

* [Misc] Add missing SchemaBaseLogic state check

- Missed this in elastic#99548

* Update x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx

Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Jason Stoltzfus <[email protected]>
  • Loading branch information
3 people committed May 12, 2021
1 parent 5d7205a commit a8ce93d
Show file tree
Hide file tree
Showing 12 changed files with 630 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 from 'react';

import { shallow } from 'enzyme';

import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';

import { EmptyState } from './';

describe('EmptyState', () => {
it('renders', () => {
const wrapper = shallow(<EmptyState />)
.find(EuiEmptyPrompt)
.dive();

expect(wrapper.find('h2').text()).toEqual('Create a schema');
expect(wrapper.find(EuiButton).prop('href')).toEqual(
expect.stringContaining('#indexing-documents-guide-schema')
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 from 'react';

import { EuiPanel, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { DOCS_PREFIX } from '../../../routes';

export const EmptyState: React.FC = () => {
return (
<EuiPanel color="subdued">
<EuiEmptyPrompt
iconType="database"
title={
<h2>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.empty.title', {
defaultMessage: 'Create a schema',
})}
</h2>
}
body={
<p>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.empty.description', {
defaultMessage:
'Create schema fields in advance, or index some documents and a schema will be created for you.',
})}
</p>
}
actions={
<EuiButton
size="s"
target="_blank"
iconType="popout"
href={`${DOCS_PREFIX}/indexing-documents-guide.html#indexing-documents-guide-schema`}
>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.empty.buttonLabel', {
defaultMessage: 'Read the indexing schema guide',
})}
</EuiButton>
}
/>
</EuiPanel>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.
*/

export { SchemaCallouts } from './schema_callouts';
export { SchemaTable } from './schema_table';
export { EmptyState } from './empty_state';
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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 { setMockValues, setMockActions } from '../../../../__mocks__';
import '../../../__mocks__/engine_logic.mock';

import React from 'react';

import { shallow } from 'enzyme';

import { SchemaErrorsCallout } from '../../../../shared/schema';

import {
UnsearchedFieldsCallout,
UnconfirmedFieldsCallout,
ConfirmSchemaButton,
} from './schema_callouts';

import { SchemaCallouts } from './';

describe('SchemaCallouts', () => {
const values = {
hasUnconfirmedFields: false,
hasNewUnsearchedFields: false,
mostRecentIndexJob: {
hasErrors: false,
activeReindexJobId: 'some-id',
},
};

beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
});

it('renders nothing if there is nothing to call out', () => {
const wrapper = shallow(<SchemaCallouts />);

expect(wrapper.text()).toBeFalsy();
});

it('renders a schema errors callout if the most recent index job had errors', () => {
setMockValues({
...values,
mostRecentIndexJob: {
hasErrors: true,
activeReindexJobId: '12345',
},
});
const wrapper = shallow(<SchemaCallouts />);

expect(wrapper.find(SchemaErrorsCallout)).toHaveLength(1);
expect(wrapper.find(SchemaErrorsCallout).prop('viewErrorsPath')).toEqual(
'/engines/some-engine/schema/reindex_job/12345'
);
});

it('renders an unsearched fields callout if the schema has new unconfirmed & unsearched fields', () => {
setMockValues({
...values,
hasUnconfirmedFields: true,
hasNewUnsearchedFields: true,
});
const wrapper = shallow(<SchemaCallouts />);

expect(wrapper.find(UnsearchedFieldsCallout)).toHaveLength(1);
});

it('renders an unconfirmed fields callout if the schema has unconfirmed fields', () => {
setMockValues({
...values,
hasUnconfirmedFields: true,
});
const wrapper = shallow(<SchemaCallouts />);

expect(wrapper.find(UnconfirmedFieldsCallout)).toHaveLength(1);
});

describe('UnsearchedFieldsCallout', () => {
it('renders an info callout about unsearched fields with a link to the relevance tuning page', () => {
const wrapper = shallow(<UnsearchedFieldsCallout />);

expect(wrapper.prop('title')).toEqual(
'Recently added fields are not being searched by default'
);
expect(wrapper.find('[data-test-subj="relevanceTuningButtonLink"]').prop('to')).toEqual(
'/engines/some-engine/relevance_tuning'
);
});
});

describe('UnconfirmedFieldsCallout', () => {
it('renders an info callout about unconfirmed fields', () => {
const wrapper = shallow(<UnconfirmedFieldsCallout />);

expect(wrapper.prop('title')).toEqual("You've recently added new schema fields");
});
});

describe('ConfirmSchemaButton', () => {
const actions = { updateSchema: jest.fn() };

beforeEach(() => {
setMockValues({ isUpdating: false });
setMockActions(actions);
});

it('allows users to confirm schema without changes from the callouts', () => {
const wrapper = shallow(<ConfirmSchemaButton />);

wrapper.simulate('click');
expect(actions.updateSchema).toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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 from 'react';

import { useValues, useActions } from 'kea';

import { EuiCallOut, EuiButton, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { EuiButtonTo } from '../../../../shared/react_router_helpers';
import { SchemaErrorsCallout } from '../../../../shared/schema';
import { ENGINE_RELEVANCE_TUNING_PATH, ENGINE_REINDEX_JOB_PATH } from '../../../routes';
import { generateEnginePath } from '../../engine';

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

export const SchemaCallouts: React.FC = () => {
const {
hasUnconfirmedFields,
hasNewUnsearchedFields,
mostRecentIndexJob: { hasErrors, activeReindexJobId },
} = useValues(SchemaLogic);

return (
<>
{hasErrors && (
<>
<SchemaErrorsCallout
viewErrorsPath={generateEnginePath(ENGINE_REINDEX_JOB_PATH, {
reindexJobId: activeReindexJobId,
})}
/>
<EuiSpacer />
</>
)}
{hasUnconfirmedFields && (
<>
{hasNewUnsearchedFields ? <UnsearchedFieldsCallout /> : <UnconfirmedFieldsCallout />}
<EuiSpacer />
</>
)}
</>
);
};

export const UnsearchedFieldsCallout: React.FC = () => (
<EuiCallOut
iconType="iInCircle"
title={i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.title', {
defaultMessage: 'Recently added fields are not being searched by default',
})}
data-test-subj="schemaUnsearchedUnconfirmedFieldsCallout"
>
<p>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.description',
{
defaultMessage:
'If these new fields should be searchable, update your search settings to include them. If you want them to remain unsearchable, confirm your new field types to dismiss this alert.',
}
)}
</p>
<EuiFlexGroup gutterSize="s" responsive={false}>
<EuiFlexItem grow={false}>
<EuiButtonTo
fill
size="s"
to={generateEnginePath(ENGINE_RELEVANCE_TUNING_PATH)}
data-test-subj="relevanceTuningButtonLink"
>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.searchSettingsButtonLabel',
{ defaultMessage: 'Update search settings' }
)}
</EuiButtonTo>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ConfirmSchemaButton />
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);

export const UnconfirmedFieldsCallout: React.FC = () => (
<EuiCallOut
iconType="iInCircle"
title={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields.title',
{ defaultMessage: "You've recently added new schema fields" }
)}
data-test-subj="schemaUnconfirmedFieldsCallout"
>
<p>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields.description',
{
defaultMessage:
'Set your new schema field(s) to their correct or expected types, and then confirm your field types.',
}
)}
</p>
<ConfirmSchemaButton />
</EuiCallOut>
);

export const ConfirmSchemaButton: React.FC = () => {
const { updateSchema } = useActions(SchemaLogic);
const { isUpdating } = useValues(SchemaLogic);

return (
<EuiButton
size="s"
isLoading={isUpdating}
onClick={() => updateSchema()}
data-test-subj="confirmSchemaTypesButton"
>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.confirmSchemaButtonLabel', {
defaultMessage: 'Confirm types',
})}
</EuiButton>
);
};
Loading

0 comments on commit a8ce93d

Please sign in to comment.