Skip to content

Commit

Permalink
[App Search] Added all Document related routes and logic (elastic#83324)
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonStoltz committed Nov 17, 2020
1 parent 112e3c7 commit 1c3b410
Show file tree
Hide file tree
Showing 9 changed files with 478 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* 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 { resetContext } from 'kea';

import { mockHttpValues } from '../../../__mocks__';
jest.mock('../../../shared/http', () => ({
HttpLogic: { values: mockHttpValues },
}));
const { http } = mockHttpValues;

jest.mock('../engine', () => ({
EngineLogic: { values: { engineName: 'engine1' } },
}));

jest.mock('../../../shared/flash_messages', () => ({
setQueuedSuccessMessage: jest.fn(),
flashAPIErrors: jest.fn(),
}));
import { setQueuedSuccessMessage, flashAPIErrors } from '../../../shared/flash_messages';

import { DocumentDetailLogic } from './document_detail_logic';

describe('DocumentDetailLogic', () => {
const DEFAULT_VALUES = {
dataLoading: true,
fields: [],
};

const mount = (defaults?: object) => {
if (!defaults) {
resetContext({});
} else {
resetContext({
defaults: {
enterprise_search: {
app_search: {
document_detail_logic: {
...defaults,
},
},
},
},
});
}
DocumentDetailLogic.mount();
};

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

describe('actions', () => {
describe('setFields', () => {
it('should set fields to the provided value and dataLoading to false', () => {
const fields = [{ name: 'foo', value: ['foo'], type: 'string' }];

mount({
dataLoading: true,
fields: [],
});

DocumentDetailLogic.actions.setFields(fields);

expect(DocumentDetailLogic.values).toEqual({
...DEFAULT_VALUES,
dataLoading: false,
fields,
});
});
});

describe('getDocumentDetails', () => {
it('will call an API endpoint and then store the result', async () => {
const fields = [{ name: 'name', value: 'python', type: 'string' }];
jest.spyOn(DocumentDetailLogic.actions, 'setFields');
const promise = Promise.resolve({ fields });
http.get.mockReturnValue(promise);

DocumentDetailLogic.actions.getDocumentDetails('1');

expect(http.get).toHaveBeenCalledWith(`/api/app_search/engines/engine1/documents/1`);
await promise;
expect(DocumentDetailLogic.actions.setFields).toHaveBeenCalledWith(fields);
});

it('handles errors', async () => {
mount();
const promise = Promise.reject('An error occurred');
http.get.mockReturnValue(promise);

try {
DocumentDetailLogic.actions.getDocumentDetails('1');
await promise;
} catch {
// Do nothing
}
expect(flashAPIErrors).toHaveBeenCalledWith('An error occurred');
});
});

describe('deleteDocument', () => {
let confirmSpy: any;
let promise: Promise<any>;

beforeEach(() => {
confirmSpy = jest.spyOn(window, 'confirm');
confirmSpy.mockImplementation(jest.fn(() => true));
promise = Promise.resolve({});
http.delete.mockReturnValue(promise);
});

afterEach(() => {
confirmSpy.mockRestore();
});

it('will call an API endpoint and show a success message', async () => {
mount();
DocumentDetailLogic.actions.deleteDocument('1');

expect(http.delete).toHaveBeenCalledWith(`/api/app_search/engines/engine1/documents/1`);
await promise;
expect(setQueuedSuccessMessage).toHaveBeenCalledWith(
'Successfully marked document for deletion. It will be deleted momentarily.'
);
});

it('will do nothing if not confirmed', async () => {
mount();
window.confirm = () => false;

DocumentDetailLogic.actions.deleteDocument('1');

expect(http.delete).not.toHaveBeenCalled();
await promise;
});

it('handles errors', async () => {
mount();
promise = Promise.reject('An error occured');
http.delete.mockReturnValue(promise);

try {
DocumentDetailLogic.actions.deleteDocument('1');
await promise;
} catch {
// Do nothing
}
expect(flashAPIErrors).toHaveBeenCalledWith('An error occured');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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 { kea, MakeLogicType } from 'kea';
import { i18n } from '@kbn/i18n';

import { HttpLogic } from '../../../shared/http';
import { EngineLogic } from '../engine';
import { flashAPIErrors, setQueuedSuccessMessage } from '../../../shared/flash_messages';
import { FieldDetails } from './types';

interface DocumentDetailLogicValues {
dataLoading: boolean;
fields: FieldDetails[];
}

interface DocumentDetailLogicActions {
setFields(fields: FieldDetails[]): { fields: FieldDetails[] };
deleteDocument(documentId: string): { documentId: string };
getDocumentDetails(documentId: string): { documentId: string };
}

type DocumentDetailLogicType = MakeLogicType<DocumentDetailLogicValues, DocumentDetailLogicActions>;

const CONFIRM_DELETE = i18n.translate(
'xpack.enterpriseSearch.appSearch.documentDetail.confirmDelete',
{
defaultMessage: 'Are you sure you want to delete this document?',
}
);
const DELETE_SUCCESS = i18n.translate(
'xpack.enterpriseSearch.appSearch.documentDetail.deleteSuccess',
{
defaultMessage: 'Successfully marked document for deletion. It will be deleted momentarily.',
}
);

export const DocumentDetailLogic = kea<DocumentDetailLogicType>({
path: ['enterprise_search', 'app_search', 'document_detail_logic'],
actions: () => ({
setFields: (fields) => ({ fields }),
getDocumentDetails: (documentId) => ({ documentId }),
deleteDocument: (documentId) => ({ documentId }),
}),
reducers: () => ({
dataLoading: [
true,
{
setFields: () => false,
},
],
fields: [
[],
{
setFields: (_, { fields }) => fields,
},
],
}),
listeners: ({ actions }) => ({
getDocumentDetails: async ({ documentId }) => {
const { engineName } = EngineLogic.values;

try {
const { http } = HttpLogic.values;
// TODO: Handle 404s
const response = await http.get(
`/api/app_search/engines/${engineName}/documents/${documentId}`
);
actions.setFields(response.fields);
} catch (e) {
flashAPIErrors(e);
}
},
deleteDocument: async ({ documentId }) => {
const { engineName } = EngineLogic.values;

if (window.confirm(CONFIRM_DELETE)) {
try {
const { http } = HttpLogic.values;
await http.delete(`/api/app_search/engines/${engineName}/documents/${documentId}`);
setQueuedSuccessMessage(DELETE_SUCCESS);
// TODO Handle routing after success
} catch (e) {
flashAPIErrors(e);
}
}
},
}),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 { resetContext } from 'kea';

import { DocumentsLogic } from './documents_logic';

describe('DocumentsLogic', () => {
const DEFAULT_VALUES = {
isDocumentCreationOpen: false,
};

const mount = (defaults?: object) => {
if (!defaults) {
resetContext({});
} else {
resetContext({
defaults: {
enterprise_search: {
app_search: {
documents_logic: {
...defaults,
},
},
},
},
});
}
DocumentsLogic.mount();
};

describe('actions', () => {
describe('openDocumentCreation', () => {
it('should toggle isDocumentCreationOpen to true', () => {
mount({
isDocumentCreationOpen: false,
});

DocumentsLogic.actions.openDocumentCreation();

expect(DocumentsLogic.values).toEqual({
...DEFAULT_VALUES,
isDocumentCreationOpen: true,
});
});
});

describe('closeDocumentCreation', () => {
it('should toggle isDocumentCreationOpen to false', () => {
mount({
isDocumentCreationOpen: true,
});

DocumentsLogic.actions.closeDocumentCreation();

expect(DocumentsLogic.values).toEqual({
...DEFAULT_VALUES,
isDocumentCreationOpen: false,
});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 { kea, MakeLogicType } from 'kea';

interface DocumentsLogicValues {
isDocumentCreationOpen: boolean;
}

interface DocumentsLogicActions {
closeDocumentCreation(): void;
openDocumentCreation(): void;
}

type DocumentsLogicType = MakeLogicType<DocumentsLogicValues, DocumentsLogicActions>;

export const DocumentsLogic = kea<DocumentsLogicType>({
path: ['enterprise_search', 'app_search', 'documents_logic'],
actions: () => ({
openDocumentCreation: true,
closeDocumentCreation: true,
}),
reducers: () => ({
isDocumentCreationOpen: [
false,
{
openDocumentCreation: () => true,
closeDocumentCreation: () => false,
},
],
}),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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 { DocumentDetailLogic } from './document_detail_logic';
export { DocumentsLogic } from './documents_logic';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 interface FieldDetails {
name: string;
value: string | string[];
type: string;
}
Loading

0 comments on commit 1c3b410

Please sign in to comment.