diff --git a/public/components/notebooks/components/__tests__/__snapshots__/notebook.test.tsx.snap b/public/components/notebooks/components/__tests__/__snapshots__/notebook.test.tsx.snap index 90933ace6d..b2bc5ba057 100644 --- a/public/components/notebooks/components/__tests__/__snapshots__/notebook.test.tsx.snap +++ b/public/components/notebooks/components/__tests__/__snapshots__/notebook.test.tsx.snap @@ -1,6 +1,344 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` spec renders the component 1`] = ` +exports[` spec Renders the empty component 1`] = ` +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+

+ sample-notebook-1 +

+
+
+
+
+
+

+ Created +
+ + 12/14/2023 06:49 PM +

+
+
+
+
+
+
+
+
+
+

+ No paragraphs +

+
+ Add a paragraph to compose your document or story. Notebooks now support two types of input: +
+
+
+
+
+
+
+
+
+ +
+
+ + Code block + +
+

+ Write contents directly using markdown, SQL or PPL. +

+
+
+ +
+
+
+
+
+ +
+
+ + Visualization + +
+

+ Import OpenSearch Dashboards or Observability visualizations to the notes. +

+
+
+ +
+
+
+
+
+
+
+
+
+
+`; + +exports[` spec Renders the visualization component 1`] = `
@@ -313,7 +651,7 @@ exports[` spec renders the component 1`] = `
`; -exports[` spec renders the empty component 1`] = ` +exports[` spec test reporting action button 1`] = `
@@ -354,14 +692,19 @@ exports[` spec renders the empty component 1`] = ` > + > + + @@ -374,7 +717,48 @@ exports[` spec renders the empty component 1`] = `
+ > +
+
+
+ +
+
+
+
@@ -394,14 +778,19 @@ exports[` spec renders the empty component 1`] = ` > + > + + @@ -418,7 +807,9 @@ exports[` spec renders the empty component 1`] = ` />

+ > + sample-notebook-1 +

@@ -436,7 +827,7 @@ exports[` spec renders the empty component 1`] = ` Created
- Invalid date + 12/14/2023 06:49 PM

@@ -487,14 +878,18 @@ exports[` spec renders the empty component 1`] = ` > + > + +
spec renders the empty component 1`] = ` > + > + +
({ describe(' spec', () => { configure({ adapter: new Adapter() }); + const props = { + loading: false, + fetchNotebooks: jest.fn(), + addSampleNotebooks: jest.fn(), + createNotebook: jest.fn(), + renameNotebook: jest.fn(), + cloneNotebook: jest.fn(), + deleteNotebook: jest.fn(), + parentBreadcrumb: { href: 'parent-href', text: 'parent-text' }, + setBreadcrumbs: jest.fn(), + setToast: jest.fn(), + }; + + const renderNoteTable = (overrides = {}) => { + const utils = render(); + // Additional setup or assertions if needed + return utils; + }; + + afterEach(() => { + cleanup(); // Cleanup the rendered component after each test + }); + it('renders the empty component', () => { - const fetchNotebooks = jest.fn(); - const addSampleNotebooks = jest.fn(); - const createNotebook = jest.fn(); - const renameNotebook = jest.fn(); - const cloneNotebook = jest.fn(); - const deleteNotebook = jest.fn(); - const setBreadcrumbs = jest.fn(); - const setToast = jest.fn(); - const utils = render( - - ); + const utils = renderNoteTable({ notebooks: [] }); expect(utils.container.firstChild).toMatchSnapshot(); }); it('renders the component', () => { - const fetchNotebooks = jest.fn(); - const addSampleNotebooks = jest.fn(); - const createNotebook = jest.fn(); - const renameNotebook = jest.fn(); - const cloneNotebook = jest.fn(); - const deleteNotebook = jest.fn(); - const setBreadcrumbs = jest.fn(); - const setToast = jest.fn(); const notebooks = Array.from({ length: 5 }, (v, k) => ({ path: `path-${k}`, id: `id-${k}`, dateCreated: '2023-01-01 12:00:00', dateModified: '2023-01-02 12:00:00', })); - const utils = render( - - ); + const utils = renderNoteTable({ notebooks }); expect(utils.container.firstChild).toMatchSnapshot(); - utils.getByText('Actions').click(); - utils.getByText('Add samples').click(); - utils.getAllByLabelText('Select this row')[0].click(); - utils.getByText('Actions').click(); - utils.getByText('Delete').click(); - utils.getByText('Cancel').click(); - utils.getAllByLabelText('Select this row')[0].click(); - utils.getByText('Actions').click(); - utils.getByText('Rename').click(); + fireEvent.click(utils.getByText('Actions')); + fireEvent.click(utils.getByText('Add samples')); + fireEvent.click(utils.getAllByLabelText('Select this row')[0]); + fireEvent.click(utils.getByText('Actions')); + fireEvent.click(utils.getByText('Delete')); + fireEvent.click(utils.getByText('Cancel')); + fireEvent.click(utils.getAllByLabelText('Select this row')[0]); + fireEvent.click(utils.getByText('Actions')); + fireEvent.click(utils.getByText('Rename')); }); - it('create notebook', async () => { - const fetchNotebooks = jest.fn(); - const addSampleNotebooks = jest.fn(); - const createNotebook = jest.fn(); - const renameNotebook = jest.fn(); - const cloneNotebook = jest.fn(); - const deleteNotebook = jest.fn(); - const setBreadcrumbs = jest.fn(); - const setToast = jest.fn(); + it('create notebook modal', async () => { const notebooks = Array.from({ length: 5 }, (v, k) => ({ path: `path-${k}`, id: `id-${k}`, dateCreated: 'date-created', dateModified: 'date-modified', })); - const utils = render( - - ); - utils.getByText('Create notebook').click(); + const utils = renderNoteTable({ notebooks }); + fireEvent.click(utils.getByText('Create notebook')); await waitFor(() => { expect(global.window.location.href).toContain('/create'); }); }); + + it('filters notebooks based on search input', () => { + const { getByPlaceholderText, getAllByText, queryByText } = renderNoteTable({ + notebooks: [ + { + path: 'path-1', + id: 'id-1', + dateCreated: 'date-created', + dateModified: 'date-modified', + }, + ], + }); + + const searchInput = getByPlaceholderText('Search notebook name'); + fireEvent.change(searchInput, { target: { value: 'path-1' } }); + + // Assert that only the matching notebook is displayed + expect(getAllByText('path-1')).toHaveLength(1); + expect(queryByText('path-0')).toBeNull(); + expect(queryByText('path-2')).toBeNull(); + }); + + it('displays empty state message and create notebook button', () => { + const { getAllByText, getAllByTestId } = renderNoteTable({ notebooks: [] }); + + expect(getAllByText('No notebooks')).toHaveLength(1); + + // Create notebook using the modal + fireEvent.click(getAllByText('Create notebook')[0]); + fireEvent.click(getAllByTestId('custom-input-modal-input')[0]); + fireEvent.input(getAllByTestId('custom-input-modal-input')[0], { + target: { value: 'test-notebook' }, + }); + fireEvent.click(getAllByText('Create')[0]); + expect(props.createNotebook).toHaveBeenCalledTimes(1); + }); + + it('renames a notebook', () => { + const notebooks = [ + { + path: 'path-1', + id: 'id-1', + dateCreated: 'date-created', + dateModified: 'date-modified', + }, + ]; + const { getByText, getByLabelText, getAllByText, getByTestId } = renderNoteTable({ notebooks }); + + // Select a notebook + fireEvent.click(getByLabelText('Select this row')); + + // Open Actions dropdown and click Rename + fireEvent.click(getByText('Actions')); + fireEvent.click(getByText('Rename')); + + // Ensure the modal is open (you may need to adjust based on your modal implementation) + expect(getAllByText('Rename notebook')).toHaveLength(1); + + // Mock user input and submit + fireEvent.input(getByTestId('custom-input-modal-input'), { + target: { value: 'test-notebook-newname' }, + }); + fireEvent.click(getByTestId('custom-input-modal-confirm-button')); + + // Assert that the renameNotebook function is called + expect(props.renameNotebook).toHaveBeenCalledTimes(1); + expect(props.renameNotebook).toHaveBeenCalledWith('test-notebook-newname', 'id-1'); + }); + + it('clones a notebook', () => { + const notebooks = [ + { + path: 'path-1', + id: 'id-1', + dateCreated: 'date-created', + dateModified: 'date-modified', + }, + ]; + const { getByText, getByLabelText, getAllByText, getByTestId } = renderNoteTable({ notebooks }); + + // Select a notebook + fireEvent.click(getByLabelText('Select this row')); + + // Open Actions dropdown and click Duplicate + fireEvent.click(getByText('Actions')); + fireEvent.click(getByText('Duplicate')); + + // Ensure the modal is open (you may need to adjust based on your modal implementation) + expect(getAllByText('Duplicate notebook')).toHaveLength(1); + + // Mock user input and submit + fireEvent.input(getByTestId('custom-input-modal-input'), { + target: { value: 'new-copy' }, + }); + fireEvent.click(getByTestId('custom-input-modal-confirm-button')); + + // Assert that the cloneNotebook function is called + expect(props.cloneNotebook).toHaveBeenCalledTimes(1); + expect(props.cloneNotebook).toHaveBeenCalledWith('new-copy', 'id-1'); + }); + + it('deletes a notebook', () => { + const notebooks = [ + { + path: 'path-1', + id: 'id-1', + dateCreated: 'date-created', + dateModified: 'date-modified', + }, + ]; + const { getByText, getByLabelText, getAllByText, getByTestId } = renderNoteTable({ notebooks }); + + // Select a notebook + fireEvent.click(getByLabelText('Select this row')); + + // Open Actions dropdown and click Delete + fireEvent.click(getByText('Actions')); + fireEvent.click(getByText('Delete')); + + // Ensure the modal is open (you may need to adjust based on your modal implementation) + expect(getAllByText('Delete 1 notebook')).toHaveLength(1); + + // Mock user confirmation and submit + fireEvent.input(getByTestId('delete-notebook-modal-input'), { + target: { value: 'delete' }, + }); + fireEvent.click(getByTestId('delete-notebook-modal-delete-button')); + + // Assert that the deleteNotebook function is called + expect(props.deleteNotebook).toHaveBeenCalledTimes(1); + expect(props.deleteNotebook).toHaveBeenCalledWith(['id-1'], expect.any(String)); + }); + + it('adds sample notebooks', async () => { + const { getByText, getAllByText, getByTestId } = renderNoteTable({ notebooks: [] }); + + // Open Actions dropdown and click Add samples + fireEvent.click(getByText('Actions')); + fireEvent.click(getAllByText('Add samples')[0]); + + // Ensure the modal is open (you may need to adjust based on your modal implementation) + expect(getAllByText('Add sample notebooks')).toHaveLength(1); + + // Mock user confirmation and submit + fireEvent.click(getByTestId('confirmModalConfirmButton')); + + // Assert that the addSampleNotebooks function is called + expect(props.addSampleNotebooks).toHaveBeenCalledTimes(1); + }); + + it('closes the action panel', async () => { + const { getByText, queryByTestId } = renderNoteTable({ notebooks: [] }); + expect(queryByTestId('rename-notebook-btn')).not.toBeInTheDocument(); + + // Open Actions dropdown + fireEvent.click(getByText('Actions')); + + // Ensure the action panel is open + expect(queryByTestId('rename-notebook-btn')).toBeInTheDocument(); + + await act(async () => { + fireEvent.click(getByText('Actions')); + }); + + // Ensure the action panel is closed + expect(queryByTestId('rename-notebook-btn')).not.toBeInTheDocument(); + }); + + it('closes the delete modal', () => { + const notebooks = [ + { + path: 'path-1', + id: 'id-1', + dateCreated: 'date-created', + dateModified: 'date-modified', + }, + ]; + const { getByText, getByLabelText, queryByText } = renderNoteTable({ notebooks }); + + // Select a notebook + fireEvent.click(getByLabelText('Select this row')); + + // Open Actions dropdown and click Delete + fireEvent.click(getByText('Actions')); + fireEvent.click(getByText('Delete')); + + // Ensure the modal is open + expect(getByText('Delete 1 notebook')).toBeInTheDocument(); + + // Close the delete modal + fireEvent.click(getByText('Cancel')); + + // Ensure the delete modal is closed + expect(queryByText('Delete 1 notebook')).toBeNull(); + }); }); diff --git a/public/components/notebooks/components/__tests__/notebook.test.tsx b/public/components/notebooks/components/__tests__/notebook.test.tsx index f31508852e..eb33888af0 100644 --- a/public/components/notebooks/components/__tests__/notebook.test.tsx +++ b/public/components/notebooks/components/__tests__/notebook.test.tsx @@ -3,17 +3,27 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { fireEvent, render, waitFor } from '@testing-library/react'; -import { configure, mount, shallow } from 'enzyme'; +import '@testing-library/jest-dom'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; +import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; -import PPLService from '../../../../services/requests/ppl'; import React from 'react'; import { HttpResponse } from '../../../../../../../src/core/public'; -import httpClientMock from '../../../../../test/__mocks__/httpClientMock'; -import { sampleNotebook1 } from '../helpers/__tests__/sampleDefaultNotebooks'; -import { Notebook } from '../notebook'; -import { SavedObjectsActions } from '../../../../services/saved_objects/saved_object_client/saved_objects_actions'; +import { getOSDHttp } from '../../../../../common/utils'; +import { + addCodeBlockResponse, + clearOutputNotebook, + codeBlockNotebook, + codePlaceholderText, + emptyNotebook, + notebookPutResponse, + runCodeBlockResponse, + sampleNotebook1, +} from '../../../../../test/notebooks_constants'; import { sampleSavedVisualization } from '../../../../../test/panels_constants'; +import PPLService from '../../../../services/requests/ppl'; +import { SavedObjectsActions } from '../../../../services/saved_objects/saved_object_client/saved_objects_actions'; +import { Notebook } from '../notebook'; jest.mock('../../../../../../../src/plugins/embeddable/public', () => ({ ViewMode: { @@ -36,23 +46,27 @@ global.fetch = jest.fn(() => describe(' spec', () => { configure({ adapter: new Adapter() }); + const httpClient = getOSDHttp(); + const pplService = new PPLService(httpClient); + const setBreadcrumbs = jest.fn(); + const renameNotebook = jest.fn(); + const cloneNotebook = jest.fn(); + const deleteNotebook = jest.fn(); + const setToast = jest.fn(); + const location = jest.fn() as any; + location.search = ''; + const history = jest.fn() as any; + history.replace = jest.fn(); + history.push = jest.fn(); - it('renders the empty component', async () => { - const pplService = new PPLService(httpClientMock); - const setBreadcrumbs = jest.fn(); - const renameNotebook = jest.fn(); - const cloneNotebook = jest.fn(); - const deleteNotebook = jest.fn(); - const setToast = jest.fn(); - const location = jest.fn(); - const history = jest.fn() as any; - history.replace = jest.fn(); + it('Renders the empty component', async () => { + httpClient.get = jest.fn(() => Promise.resolve((emptyNotebook as unknown) as HttpResponse)); const utils = render( spec', () => { history={history} /> ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); expect(utils.container.firstChild).toMatchSnapshot(); - utils.getByText('Add code block').click(); - utils.getByText('Add visualization').click(); }); - it('renders the component', async () => { - const pplService = new PPLService(httpClientMock); - const setBreadcrumbs = jest.fn(); - const renameNotebook = jest.fn(); - const cloneNotebook = jest.fn(); - const deleteNotebook = jest.fn(); - const setToast = jest.fn(); - const location = jest.fn(); - const history = jest.fn() as any; - history.replace = jest.fn(); + it('test reporting action button', async () => { + httpClient.get = jest.fn(() => Promise.resolve((emptyNotebook as unknown) as HttpResponse)); + const utils = render( + + ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); + expect(utils.container.firstChild).toMatchSnapshot(); + + act(() => { + fireEvent.click(utils.getByText('Reporting actions')); + }); + + expect(utils.queryByTestId('download-notebook-pdf')).toBeInTheDocument(); + + act(() => { + fireEvent.click(utils.getByText('Reporting actions')); + }); + + await waitFor(() => { + expect(utils.queryByTestId('download-notebook-pdf')).toBeNull(); + }); + }); + + it('Adds a code block', async () => { + httpClient.get = jest.fn(() => Promise.resolve((emptyNotebook as unknown) as HttpResponse)); + let postFlag = 1; + httpClient.post = jest.fn(() => { + if (postFlag === 1) { + postFlag += 1; + return Promise.resolve((addCodeBlockResponse as unknown) as HttpResponse); + } else return Promise.resolve((runCodeBlockResponse as unknown) as HttpResponse); + }); + const utils = render( + + ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); + + act(() => { + utils.getByText('Add code block').click(); + }); + + await waitFor(() => { + expect(utils.getByPlaceholderText(codePlaceholderText)).toBeInTheDocument(); + }); + }); + + it('toggles show input in code block', async () => { + httpClient.get = jest.fn(() => Promise.resolve((emptyNotebook as unknown) as HttpResponse)); + let postFlag = 1; + httpClient.post = jest.fn(() => { + if (postFlag === 1) { + postFlag += 1; + return Promise.resolve((addCodeBlockResponse as unknown) as HttpResponse); + } else return Promise.resolve((runCodeBlockResponse as unknown) as HttpResponse); + }); + const utils = render( + + ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); + + act(() => { + utils.getByText('Add code block').click(); + }); + + await waitFor(() => { + expect(utils.getByPlaceholderText(codePlaceholderText)).toBeInTheDocument(); + }); + + act(() => { + utils.getByLabelText('Toggle show input').click(); + }); + + await waitFor(() => { + expect(utils.queryByPlaceholderText(codePlaceholderText)).toBeNull(); + }); + }); + + it('runs a code block and checks the output', async () => { + httpClient.get = jest.fn(() => Promise.resolve((emptyNotebook as unknown) as HttpResponse)); + let postFlag = 1; + httpClient.post = jest.fn(() => { + if (postFlag === 1) { + postFlag += 1; + return Promise.resolve((addCodeBlockResponse as unknown) as HttpResponse); + } else return Promise.resolve((runCodeBlockResponse as unknown) as HttpResponse); + }); + const utils = render( + + ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); + + act(() => { + utils.getByText('Add code block').click(); + }); + + await waitFor(() => { + expect(utils.getByPlaceholderText(codePlaceholderText)).toBeInTheDocument(); + }); + + act(() => { + fireEvent.input(utils.getByPlaceholderText(codePlaceholderText), { + target: { value: '%md \\n hello' }, + }); + fireEvent.click(utils.getByText('Run')); + }); + + await waitFor(() => { + expect(utils.queryByText('Run')).toBeNull(); + expect(utils.getByText('hello')).toBeInTheDocument(); + }); + }); + + it('toggles between input/output only views', async () => { + httpClient.get = jest.fn(() => Promise.resolve((emptyNotebook as unknown) as HttpResponse)); + const utils = render( + + ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); + + act(() => { + utils.getByText('Add code block').click(); + }); + + await waitFor(() => { + expect(utils.getByPlaceholderText(codePlaceholderText)).toBeInTheDocument(); + }); + + act(() => { + utils.getByLabelText('Toggle show input').click(); + }); + + await waitFor(() => { + expect(utils.queryByPlaceholderText(codePlaceholderText)).toBeNull(); + }); + + act(() => { + utils.getByLabelText('Toggle show input').click(); + }); + + act(() => { + fireEvent.click(utils.getByTestId('input_only')); + }); + + await waitFor(() => { + expect(utils.queryByText('Refresh')).toBeInTheDocument(); + }); + + act(() => { + fireEvent.click(utils.getByTestId('output_only')); + }); + + await waitFor(() => { + expect(utils.queryByText('Refresh')).toBeNull(); + expect(utils.getByText('hello')).toBeInTheDocument(); + }); + }); + + it('Renders a notebook and checks paragraph actions', async () => { + httpClient.get = jest.fn(() => Promise.resolve((codeBlockNotebook as unknown) as HttpResponse)); + httpClient.put = jest.fn(() => + Promise.resolve((clearOutputNotebook as unknown) as HttpResponse) + ); + httpClient.delete = jest.fn(() => + Promise.resolve(({ paragraphs: [] } as unknown) as HttpResponse) + ); + + const utils = render( + + ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); + + act(() => { + fireEvent.click(utils.getByText('Paragraph actions')); + }); + + act(() => { + fireEvent.click(utils.getByText('Clear all outputs')); + }); + + await waitFor(() => { + expect( + utils.queryByText( + 'Are you sure you want to clear all outputs? The action cannot be undone.' + ) + ).toBeInTheDocument(); + }); + + act(() => { + fireEvent.click(utils.getByTestId('confirmModalConfirmButton')); + }); + + await waitFor(() => { + expect(utils.queryByText('hello')).toBeNull(); + }); + + act(() => { + fireEvent.click(utils.getByText('Paragraph actions')); + }); + + act(() => { + fireEvent.click(utils.getByText('Delete all paragraphs')); + }); + + await waitFor(() => { + expect( + utils.queryByText( + 'Are you sure you want to delete all paragraphs? The action cannot be undone.' + ) + ).toBeInTheDocument(); + }); + + act(() => { + fireEvent.click(utils.getByTestId('confirmModalConfirmButton')); + }); + + await waitFor(() => { + expect(utils.queryByText('No paragraphs')).toBeInTheDocument(); + }); + }); + + it('Checks notebook rename action', async () => { + const renameNotebookMock = jest.fn(() => + Promise.resolve((notebookPutResponse as unknown) as HttpResponse) + ); + const cloneNotebookMock = jest.fn(() => Promise.resolve('dummy-string')); + httpClient.get = jest.fn(() => Promise.resolve((codeBlockNotebook as unknown) as HttpResponse)); + + httpClient.put = jest.fn(() => { + return Promise.resolve((notebookPutResponse as unknown) as HttpResponse); + }); + + httpClient.post = jest.fn(() => { + return Promise.resolve((addCodeBlockResponse as unknown) as HttpResponse); + }); + + const utils = render( + + ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); + + act(() => { + fireEvent.click(utils.getByText('Notebook actions')); + }); + + act(() => { + fireEvent.click(utils.getByText('Rename notebook')); + }); + + await waitFor(() => { + expect(utils.queryByTestId('custom-input-modal-input')).toBeInTheDocument(); + }); + + act(() => { + fireEvent.input(utils.getByTestId('custom-input-modal-input'), { + target: { value: 'test-notebook-newname' }, + }); + fireEvent.click(utils.getByTestId('custom-input-modal-confirm-button')); + }); + + await waitFor(() => { + expect(renameNotebookMock).toHaveBeenCalledTimes(1); + }); + }); + + it('Checks notebook clone action', async () => { + const renameNotebookMock = jest.fn(() => + Promise.resolve((notebookPutResponse as unknown) as HttpResponse) + ); + const cloneNotebookMock = jest.fn(() => Promise.resolve('dummy-string')); + httpClient.get = jest.fn(() => Promise.resolve((codeBlockNotebook as unknown) as HttpResponse)); + + httpClient.put = jest.fn(() => { + return Promise.resolve((notebookPutResponse as unknown) as HttpResponse); + }); + + httpClient.post = jest.fn(() => { + return Promise.resolve((addCodeBlockResponse as unknown) as HttpResponse); + }); + + const utils = render( + + ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); + + act(() => { + fireEvent.click(utils.getByText('Notebook actions')); + }); + + act(() => { + fireEvent.click(utils.getByText('Duplicate notebook')); + }); + + await waitFor(() => { + expect(utils.queryByTestId('custom-input-modal-input')).toBeInTheDocument(); + }); + + act(() => { + fireEvent.click(utils.getByTestId('custom-input-modal-confirm-button')); + }); + + expect(cloneNotebookMock).toHaveBeenCalledTimes(1); + }); + + it('Checks notebook delete action', async () => { + const renameNotebookMock = jest.fn(() => + Promise.resolve((notebookPutResponse as unknown) as HttpResponse) + ); + const cloneNotebookMock = jest.fn(() => Promise.resolve('dummy-string')); + httpClient.get = jest.fn(() => Promise.resolve((codeBlockNotebook as unknown) as HttpResponse)); + + httpClient.put = jest.fn(() => { + return Promise.resolve((notebookPutResponse as unknown) as HttpResponse); + }); + + httpClient.post = jest.fn(() => { + return Promise.resolve((addCodeBlockResponse as unknown) as HttpResponse); + }); + + const utils = render( + + ); + await waitFor(() => { + expect(utils.getByText('sample-notebook-1')).toBeInTheDocument(); + }); + + act(() => { + fireEvent.click(utils.getByText('Notebook actions')); + }); + + act(() => { + fireEvent.click(utils.getByText('Delete notebook')); + }); + + await waitFor(() => { + expect(utils.queryByTestId('delete-notebook-modal-input')).toBeInTheDocument(); + }); + + act(() => { + fireEvent.input(utils.getByTestId('delete-notebook-modal-input'), { + target: { value: 'delete' }, + }); + }); + + act(() => { + fireEvent.click(utils.getByTestId('delete-notebook-modal-delete-button')); + }); + + expect(deleteNotebook).toHaveBeenCalledTimes(1); + }); + it('Renders the visualization component', async () => { SavedObjectsActions.getBulk = jest.fn().mockResolvedValue({ observabilityObjectList: [{ savedVisualization: sampleSavedVisualization }], }); - httpClientMock.get = jest.fn(() => + httpClient.get = jest.fn(() => Promise.resolve(({ ...sampleNotebook1, path: sampleNotebook1.name, @@ -108,7 +596,7 @@ describe(' spec', () => { pplService={pplService} openedNoteId={sampleNotebook1.id} DashboardContainerByValueRenderer={jest.fn()} - http={httpClientMock} + http={httpClient} parentBreadcrumb={{ href: 'parent-href', text: 'parent-text' }} setBreadcrumbs={setBreadcrumbs} renameNotebook={renameNotebook} diff --git a/public/components/notebooks/components/helpers/__tests__/default_parser.test.tsx b/public/components/notebooks/components/helpers/__tests__/default_parser.test.tsx index 3c03a3d5b9..2339b95026 100644 --- a/public/components/notebooks/components/helpers/__tests__/default_parser.test.tsx +++ b/public/components/notebooks/components/helpers/__tests__/default_parser.test.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { defaultParagraphParser } from '../default_parser'; import { sampleNotebook1, sampleNotebook2, @@ -12,18 +11,18 @@ import { sampleNotebook5, sampleParsedParagraghs1, sampleParsedParagraghs2, -} from './sampleDefaultNotebooks'; +} from '../../../../../../test/notebooks_constants'; +import { defaultParagraphParser } from '../default_parser'; // Perfect schema describe('Testing default backend parser function with perfect schema', () => { - test('defaultParagraphParserTest1', (done) => { + it('defaultParagraphParserTest1', () => { const parsedParagraphs1 = defaultParagraphParser(sampleNotebook1.paragraphs); const parsedParagraphs2 = defaultParagraphParser(sampleNotebook2.paragraphs); const parsedParagraphs3 = defaultParagraphParser([]); expect(parsedParagraphs1).toEqual(sampleParsedParagraghs1); expect(parsedParagraphs2).toEqual(sampleParsedParagraghs2); expect(parsedParagraphs3).toEqual([]); - done(); }); it('returns parsed paragraphs', () => { @@ -82,16 +81,15 @@ describe('Testing default backend parser function with perfect schema', () => { // Issue in schema describe('Testing default backend parser function with wrong schema', () => { - test('defaultParagraphParserTest2', (done) => { + it('defaultParagraphParserTest2', () => { expect(() => { - const parsedParagraphs1 = defaultParagraphParser(sampleNotebook3.paragraphs); + const _parsedParagraphs1 = defaultParagraphParser(sampleNotebook3.paragraphs); }).toThrow(Error); expect(() => { - const parsedParagraphs2 = defaultParagraphParser(sampleNotebook4.paragraphs); + const _parsedParagraphs2 = defaultParagraphParser(sampleNotebook4.paragraphs); }).toThrow(Error); expect(() => { - const parsedParagraphs3 = defaultParagraphParser(sampleNotebook5.paragraphs); + const _parsedParagraphs3 = defaultParagraphParser(sampleNotebook5.paragraphs); }).toThrow(Error); - done(); }); }); diff --git a/public/components/notebooks/components/note_table.tsx b/public/components/notebooks/components/note_table.tsx index 9c63e00082..a25a1d7a68 100644 --- a/public/components/notebooks/components/note_table.tsx +++ b/public/components/notebooks/components/note_table.tsx @@ -36,14 +36,13 @@ import { CREATE_NOTE_MESSAGE, NOTEBOOKS_DOCUMENTATION_URL, } from '../../../../common/constants/notebooks'; -import { UI_DATE_FORMAT } from '../../../../common/constants/shared'; +import { UI_DATE_FORMAT, pageStyles } from '../../../../common/constants/shared'; import { DeleteNotebookModal, getCustomModal, getSampleNotebooksModal, } from './helpers/modal_containers'; import { NotebookType } from './main'; -import { pageStyles } from '../../../../common/constants/shared'; interface NoteTableProps { loading: boolean; @@ -222,6 +221,7 @@ export function NoteTable({ setIsActionsPopoverOpen(false); renameNote(); }} + data-test-subj="rename-notebook-btn" > Rename , @@ -232,6 +232,7 @@ export function NoteTable({ setIsActionsPopoverOpen(false); cloneNote(); }} + data-test-subj="duplicate-notebook-btn" > Duplicate , @@ -242,6 +243,7 @@ export function NoteTable({ setIsActionsPopoverOpen(false); deleteNote(); }} + data-test-subj="delete-notebook-btn" > Delete , @@ -251,6 +253,7 @@ export function NoteTable({ setIsActionsPopoverOpen(false); addSampleNotebooksModal(); }} + data-test-subj="add-samples-btn" > Add samples , diff --git a/public/components/notebooks/components/notebook.tsx b/public/components/notebooks/components/notebook.tsx index 4f22f146fc..9db0114126 100644 --- a/public/components/notebooks/components/notebook.tsx +++ b/public/components/notebooks/components/notebook.tsx @@ -6,7 +6,7 @@ import { EuiButton, EuiButtonGroup, - EuiButtonGroupOption, + EuiButtonGroupOptionProps, EuiCard, EuiContextMenu, EuiContextMenuPanelDescriptor, @@ -27,7 +27,6 @@ import moment from 'moment'; import queryString from 'query-string'; import React, { Component } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import PPLService from '../../../services/requests/ppl'; import { ChromeBreadcrumb, CoreStart } from '../../../../../../src/core/public'; import { DashboardStart } from '../../../../../../src/plugins/dashboard/public'; import { @@ -36,6 +35,7 @@ import { } from '../../../../common/constants/notebooks'; import { UI_DATE_FORMAT } from '../../../../common/constants/shared'; import { ParaType } from '../../../../common/types/notebooks'; +import PPLService from '../../../services/requests/ppl'; import { GenerateReportLoadingModal } from './helpers/custom_modals/reporting_loading_modal'; import { defaultParagraphParser } from './helpers/default_parser'; import { DeleteNotebookModal, getCustomModal, getDeleteModal } from './helpers/modal_containers'; @@ -193,7 +193,7 @@ export class Notebook extends Component { paragraphId: para.uniqueId, }, }) - .then((res) => { + .then((_res) => { const paragraphs = [...this.state.paragraphs]; paragraphs.splice(index, 1); const parsedPara = [...this.state.parsedPara]; @@ -205,6 +205,7 @@ export class Notebook extends Component { 'Error deleting paragraph, please make sure you have the correct permission.', 'danger' ); + console.error(err); }); } }; @@ -246,6 +247,7 @@ export class Notebook extends Component { 'Error deleting paragraph, please make sure you have the correct permission.', 'danger' ); + console.error(err); }); }, 'Delete all paragraphs', @@ -354,6 +356,7 @@ export class Notebook extends Component { 'Error deleting visualization, please make sure you have the correct permission.', 'danger' ); + console.error(err); }); }; @@ -388,6 +391,7 @@ export class Notebook extends Component { 'Error adding paragraph, please make sure you have the correct permission.', 'danger' ); + console.error(err); }); }; @@ -421,13 +425,14 @@ export class Notebook extends Component { .post(`${NOTEBOOKS_API_PREFIX}/set_paragraphs/`, { body: JSON.stringify(moveParaObj), }) - .then((res) => this.setState({ paragraphs, parsedPara })) - .then((res) => this.scrollToPara(targetIndex)) + .then((_res) => this.setState({ paragraphs, parsedPara })) + .then((_res) => this.scrollToPara(targetIndex)) .catch((err) => { this.props.setToast( 'Error moving paragraphs, please make sure you have the correct permission.', 'danger' ); + console.error(err); }); }; @@ -460,6 +465,7 @@ export class Notebook extends Component { 'Error clearing paragraphs, please make sure you have the correct permission.', 'danger' ); + console.error(err); }); }; @@ -530,9 +536,9 @@ export class Notebook extends Component { } }; - runForAllParagraphs = (reducer: (para: ParaType, index: number) => Promise) => { + runForAllParagraphs = (reducer: (para: ParaType, _index: number) => Promise) => { return this.state.parsedPara - .map((para: ParaType, index: number) => () => reducer(para, index)) + .map((para: ParaType, _index: number) => () => reducer(para, _index)) .reduce((chain, func) => chain.then(func), Promise.resolve()); }; @@ -588,6 +594,7 @@ export class Notebook extends Component { 'Error fetching notebooks, please make sure you have the correct permission.', 'danger' ); + console.error(err); }); }; @@ -604,6 +611,7 @@ export class Notebook extends Component { }) .catch((err) => { this.props.setToast('Error getting query output', 'danger'); + console.error(err); }); }; @@ -655,6 +663,7 @@ export class Notebook extends Component { }) .catch((error) => { this.props.setToast('Error checking Reporting Plugin Installation status.', 'danger'); + console.error(error); }); } @@ -689,7 +698,7 @@ export class Notebook extends Component {

); - const viewOptions: EuiButtonGroupOption[] = [ + const viewOptions: EuiButtonGroupOptionProps[] = [ { id: 'view_both', label: 'View both', @@ -743,7 +752,7 @@ export class Notebook extends Component { disabled: this.state.parsedPara.length === 0, onClick: () => { this.setState({ isParaActionsPopoverOpen: false }); - this.runForAllParagraphs((para: ParaType, index: number) => { + this.runForAllParagraphs((para: ParaType, _index: number) => { return para.paraRef.current?.runParagraph(); }); if (this.state.selectedViewId === 'input_only') { @@ -847,7 +856,7 @@ export class Notebook extends Component { items: [ { name: 'Download PDF', - icon: , + icon: , onClick: () => { this.setState({ isReportingActionsPopoverOpen: false }); generateInContextReport('pdf', this.props, this.toggleReportingLoadingModal); @@ -890,7 +899,11 @@ export class Notebook extends Component { id="reportingActionsButton" iconType="arrowDown" iconSide="right" - onClick={() => this.setState({ isReportingActionsPopoverOpen: true })} + onClick={() => + this.setState({ + isReportingActionsPopoverOpen: !this.state.isReportingActionsPopoverOpen, + }) + } > Reporting actions @@ -922,6 +935,7 @@ export class Notebook extends Component { onChange={(id) => { this.updateView(id); }} + legend="notebook view buttons" /> )} @@ -935,7 +949,11 @@ export class Notebook extends Component { data-test-subj="notebook-paragraph-actions-button" iconType="arrowDown" iconSide="right" - onClick={() => this.setState({ isParaActionsPopoverOpen: true })} + onClick={() => + this.setState({ + isParaActionsPopoverOpen: !this.state.isParaActionsPopoverOpen, + }) + } > Paragraph actions @@ -955,7 +973,11 @@ export class Notebook extends Component { data-test-subj="notebook-notebook-actions-button" iconType="arrowDown" iconSide="right" - onClick={() => this.setState({ isNoteActionsPopoverOpen: true })} + onClick={() => + this.setState({ + isNoteActionsPopoverOpen: !this.state.isNoteActionsPopoverOpen, + }) + } > Notebook actions diff --git a/public/components/notebooks/components/paragraph_components/__tests__/__snapshots__/para_output.test.tsx.snap b/public/components/notebooks/components/paragraph_components/__tests__/__snapshots__/para_output.test.tsx.snap index e8c496b7cc..029f9e027c 100644 --- a/public/components/notebooks/components/paragraph_components/__tests__/__snapshots__/para_output.test.tsx.snap +++ b/public/components/notebooks/components/paragraph_components/__tests__/__snapshots__/para_output.test.tsx.snap @@ -1,5 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` spec renders dashboards visualization outputs 1`] = ` +
+ 2020-Jul-21 18:37:44 - 2020-Aug-20 18:37:44 +
+`; + exports[` spec renders markdown outputs 1`] = `
spec renders markdown outputs 1`] = `
`; +exports[` spec renders observability visualization outputs 1`] = ` +
+ 2020-Jul-21 18:37:44 - 2020-Aug-20 18:37:44 +
+`; + exports[` spec renders other types of outputs 1`] = `
spec renders query outputs 1`] = `
`; -exports[` spec renders visualization outputs 1`] = ` +exports[` spec renders query outputs with error 1`] = `
- 2020-Jul-21 18:37:44 - 2020-Aug-20 18:37:44 +
+    
+      {"error":"Invalid SQL query"}
+    
+  
`; diff --git a/public/components/notebooks/components/paragraph_components/__tests__/para_input.test.tsx b/public/components/notebooks/components/paragraph_components/__tests__/para_input.test.tsx index de07ab37d0..4841064a03 100644 --- a/public/components/notebooks/components/paragraph_components/__tests__/para_input.test.tsx +++ b/public/components/notebooks/components/paragraph_components/__tests__/para_input.test.tsx @@ -7,7 +7,7 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import React from 'react'; -import { sampleParsedParagraghs1 } from '../../helpers/__tests__/sampleDefaultNotebooks'; +import { sampleParsedParagraghs1 } from '../../../../../../test/notebooks_constants'; import { ParaInput } from '../para_input'; describe(' spec', () => { diff --git a/public/components/notebooks/components/paragraph_components/__tests__/para_output.test.tsx b/public/components/notebooks/components/paragraph_components/__tests__/para_output.test.tsx index 3b2d2e9faf..a7f35a6a07 100644 --- a/public/components/notebooks/components/paragraph_components/__tests__/para_output.test.tsx +++ b/public/components/notebooks/components/paragraph_components/__tests__/para_output.test.tsx @@ -7,12 +7,24 @@ import { render } from '@testing-library/react'; import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import React from 'react'; -import { sampleParsedParagraghs1 } from '../../helpers/__tests__/sampleDefaultNotebooks'; +import { Provider } from 'react-redux'; +import { legacy_createStore as createStore } from 'redux'; +import { + getOSDHttp, + setPPLService, + uiSettingsService, +} from '../../../../../../common/utils/core_services'; +import { + sampleObservabilityVizParagraph, + sampleParsedParagraghs1, +} from '../../../../../../test/notebooks_constants'; +import { rootReducer } from '../../../../../framework/redux/reducers'; +import PPLService from '../../../../../services/requests/ppl'; import { ParaOutput } from '../para_output'; -import { uiSettingsService } from '../../../../../../common/utils/core_services'; describe(' spec', () => { configure({ adapter: new Adapter() }); + const store = createStore(rootReducer); it('renders markdown outputs', () => { const para = sampleParsedParagraghs1[0]; @@ -46,7 +58,24 @@ describe(' spec', () => { expect(utils.container.firstChild).toMatchSnapshot(); }); - it('renders visualization outputs', () => { + it('renders query outputs with error', () => { + const para = sampleParsedParagraghs1[3]; + para.out = ['{"error":"Invalid SQL query"}']; + para.isSelected = true; + const setVisInput = jest.fn(); + const utils = render( + + ); + expect(utils.container.firstChild).toMatchSnapshot(); + }); + + it('renders dashboards visualization outputs', () => { const para = sampleParsedParagraghs1[2]; para.isSelected = true; @@ -67,6 +96,30 @@ describe(' spec', () => { expect(utils.container.firstChild).toMatchSnapshot(); }); + it('renders observability visualization outputs', () => { + setPPLService(new PPLService(getOSDHttp())); + const para = sampleObservabilityVizParagraph; + para.isSelected = true; + + uiSettingsService.get = jest.fn().mockReturnValue('YYYY-MMM-DD HH:mm:ss'); + const setVisInput = jest.fn(); + const utils = render( + + null} + /> + + ); + expect(utils.container.textContent).toMatch('2020-Jul-21 18:37:44 - 2020-Aug-20 18:37:44'); + expect(utils.container.firstChild).toMatchSnapshot(); + }); + it('renders other types of outputs', () => { const para = sampleParsedParagraghs1[0]; para.isSelected = true; diff --git a/public/components/notebooks/components/paragraph_components/__tests__/paragraphs.test.tsx b/public/components/notebooks/components/paragraph_components/__tests__/paragraphs.test.tsx index 710499a998..f2b054ff3a 100644 --- a/public/components/notebooks/components/paragraph_components/__tests__/paragraphs.test.tsx +++ b/public/components/notebooks/components/paragraph_components/__tests__/paragraphs.test.tsx @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { fireEvent, render } from '@testing-library/react'; -import { configure, mount, shallow } from 'enzyme'; +import { render } from '@testing-library/react'; +import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import React from 'react'; -import httpClientMock from '../../../../../../test/__mocks__/httpClientMock'; -import { sampleParsedParagraghs1 } from '../../helpers/__tests__/sampleDefaultNotebooks'; +import { getOSDHttp } from '../../../../../../common/utils'; +import { sampleParsedParagraghs1 } from '../../../../../../test/notebooks_constants'; import { Paragraphs } from '../paragraphs'; jest.mock('../../../../../../../../src/plugins/embeddable/public', () => ({ @@ -50,7 +50,7 @@ describe(' spec', () => { addPara={addPara} DashboardContainerByValueRenderer={DashboardContainerByValueRenderer} deleteVizualization={deleteVizualization} - http={httpClientMock} + http={getOSDHttp()} selectedViewId="view_both" setSelectedViewId={setSelectedViewId} deletePara={deletePara} diff --git a/public/components/notebooks/components/paragraph_components/para_output.tsx b/public/components/notebooks/components/paragraph_components/para_output.tsx index dfd8a585d3..6fee9c66c3 100644 --- a/public/components/notebooks/components/paragraph_components/para_output.tsx +++ b/public/components/notebooks/components/paragraph_components/para_output.tsx @@ -6,9 +6,8 @@ import { EuiCodeBlock, EuiSpacer, EuiText } from '@elastic/eui'; import MarkdownRender from '@nteract/markdown'; import { Media } from '@nteract/outputs'; -import React, { useState } from 'react'; -import { VisualizationContainer } from '../../../../components/custom_panels/panel_modules/visualization_container'; -import PPLService from '../../../../services/requests/ppl'; +import moment from 'moment'; +import React from 'react'; import { CoreStart } from '../../../../../../../src/core/public'; import { DashboardContainerInput, @@ -16,8 +15,9 @@ import { } from '../../../../../../../src/plugins/dashboard/public'; import { ParaType } from '../../../../../common/types/notebooks'; import { getOSDHttp, getPPLService, uiSettingsService } from '../../../../../common/utils'; +import { VisualizationContainer } from '../../../../components/custom_panels/panel_modules/visualization_container'; +import PPLService from '../../../../services/requests/ppl'; import { QueryDataGridMemo } from './para_query_grid'; -import { convertDateTime } from '../../../common/query_utils'; const createQueryColumns = (jsonColumns: any[]) => { let index = 0; @@ -53,44 +53,19 @@ const getQueryOutputData = (queryObject: any) => { return data; }; -const QueryPara = ({ inp, val }) => { - const inputQuery = inp.substring(4, inp.length); - const queryObject = JSON.parse(val); - - const columns = createQueryColumns(queryObject.schema); - const [visibleColumns, setVisibleColumns] = useState(columns.map((c) => c.id)); - const data = getQueryOutputData(queryObject); - - return queryObject.hasOwnProperty('error') ? ( - {val} - ) : ( -
- - {inputQuery} - - - -
- ); -}; - const OutputBody = ({ + key, typeOut, val, - inp, + para, visInput, setVisInput, DashboardContainerByValueRenderer, }: { + key: string; typeOut: string; val: string; - inp: string; + para: ParaType; visInput: DashboardContainerInput; setVisInput: (input: DashboardContainerInput) => void; DashboardContainerByValueRenderer: DashboardStart['DashboardContainerByValueRenderer']; @@ -99,74 +74,100 @@ const OutputBody = ({ * Currently supports HTML, TABLE, IMG * TODO: add table rendering */ + const dateFormat = uiSettingsService.get('dateFormat'); - const from = convertDateTime(visInput?.timeRange?.from, true, false); - const to = convertDateTime(visInput?.timeRange?.to, false, false); - const displayFrom = - convertDateTime(visInput?.timeRange?.from, true, dateFormat) || 'Invalid date'; - const displayTo = convertDateTime(visInput?.timeRange?.to, false, dateFormat) || 'Invalid date'; + if (typeOut !== undefined) { switch (typeOut) { case 'QUERY': - return ; + const inputQuery = para.inp.substring(4, para.inp.length); + const queryObject = JSON.parse(val); + if (queryObject.hasOwnProperty('error')) { + return {val}; + } else { + const columns = createQueryColumns(queryObject.schema); + const data = getQueryOutputData(queryObject); + return ( +
+ + {inputQuery} + + + +
+ ); + } case 'MARKDOWN': return ( - + ); case 'VISUALIZATION': + let from = moment(visInput?.timeRange?.from).format(dateFormat); + let to = moment(visInput?.timeRange?.to).format(dateFormat); + from = from === 'Invalid date' ? visInput.timeRange.from : from; + to = to === 'Invalid date' ? visInput.timeRange.to : to; return ( <> - {`${displayFrom} - ${displayTo}`} + {`${from} - ${to}`} - + ); case 'OBSERVABILITY_VISUALIZATION': - const http = getOSDHttp(); - const pplService = getPPLService(); - + let fromObs = moment(visInput?.timeRange?.from).format(dateFormat); + let toObs = moment(visInput?.timeRange?.to).format(dateFormat); + fromObs = fromObs === 'Invalid date' ? visInput.timeRange.from : fromObs; + toObs = toObs === 'Invalid date' ? visInput.timeRange.to : toObs; const onEditClick = (savedVisualizationId: string) => { window.location.assign(`observability-logs#/explorer/${savedVisualizationId}`); }; return ( <> - {`${displayFrom} - ${displayTo}`} + {`${fromObs} - ${toObs}`}
); case 'HTML': return ( - + {/* eslint-disable-next-line react/jsx-pascal-case */} ); case 'TABLE': - return
{val}
; + return
{val}
; case 'IMG': - return ; + return ; default: - return
{val}
; + return
{val}
; } } else { console.log('output not supported', typeOut); @@ -193,21 +194,23 @@ export const ParaOutput = (props: { }) => { const { para, DashboardContainerByValueRenderer, visInput, setVisInput } = props; - return !para.isOutputHidden ? ( - <> - {para.typeOut.map((typeOut: string, tIdx: number) => { - return ( - - ); - })} - - ) : null; + return ( + !para.isOutputHidden && ( + <> + {para.typeOut.map((typeOut: string, tIdx: number) => { + return ( + + ); + })} + + ) + ); }; diff --git a/public/components/notebooks/components/paragraph_components/para_query_grid.tsx b/public/components/notebooks/components/paragraph_components/para_query_grid.tsx index 6321c57026..28b986076a 100644 --- a/public/components/notebooks/components/paragraph_components/para_query_grid.tsx +++ b/public/components/notebooks/components/paragraph_components/para_query_grid.tsx @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import $ from 'jquery'; import { EuiDataGrid, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import $ from 'jquery'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; interface QueryDataGridProps { rowCount: number; @@ -23,7 +23,7 @@ function QueryDataGrid(props: QueryDataGridProps) { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 }); // ** Sorting config const [sortingColumns, setSortingColumns] = useState([]); - const [visibleColumns, setVisibleColumns] = useState>([]); + const [visibleColumns, setVisibleColumns] = useState([]); const [isVisible, setIsVisible] = useState(false); @@ -108,7 +108,6 @@ function queryDataGridPropsAreEqual(prevProps: QueryDataGridProps, nextProps: Qu return ( prevProps.rowCount === nextProps.rowCount && JSON.stringify(prevProps.queryColumns) === JSON.stringify(nextProps.queryColumns) && - JSON.stringify(prevProps.visibleColumns) === JSON.stringify(nextProps.visibleColumns) && JSON.stringify(prevProps.dataValues) === JSON.stringify(nextProps.dataValues) ); } diff --git a/public/components/notebooks/components/helpers/__tests__/sampleDefaultNotebooks.tsx b/test/notebooks_constants.ts similarity index 76% rename from public/components/notebooks/components/helpers/__tests__/sampleDefaultNotebooks.tsx rename to test/notebooks_constants.ts index 17e077ed52..a936b41acd 100644 --- a/public/components/notebooks/components/helpers/__tests__/sampleDefaultNotebooks.tsx +++ b/test/notebooks_constants.ts @@ -2,6 +2,65 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +export const addCodeBlockResponse = { + id: 'paragraph_044d9850-b0b2-4034-8590-6d845c65d1a7', + dateCreated: '2023-12-18T06:27:34.320Z', + dateModified: '2023-12-18T06:27:34.320Z', + input: { inputType: 'MARKDOWN', inputText: '' }, + output: [{ outputType: 'MARKDOWN', result: '', execution_time: '0s' }], +}; + +export const runCodeBlockResponse = { + output: [{ outputType: 'MARKDOWN', result: '\n\nhello', execution_time: '0.939 ms' }], + input: { inputText: '%md \n\nhello', inputType: 'MARKDOWN' }, + dateCreated: '2023-12-18T22:13:39.627Z', + dateModified: '2023-12-18T22:17:24.853Z', + id: 'paragraph_7713f4d5-c3b2-406d-9f06-99a1fe0251f3', +}; + +export const codePlaceholderText = + 'Type %md, %sql or %ppl on the first line to define the input type. Code block starts here.'; + +export const codeBlockNotebook = { + path: 'sample-notebook-1', + dateCreated: '2023-12-14T18:49:43.375Z', + dateModified: '2023-12-18T23:40:59.500Z', + paragraphs: [ + { + output: [{ result: 'hello', outputType: 'MARKDOWN', execution_time: '0.018 ms' }], + input: { inputText: '%md\nhello', inputType: 'MARKDOWN' }, + dateCreated: '2023-12-18T23:38:50.848Z', + dateModified: '2023-12-18T23:39:12.265Z', + id: 'paragraph_de00ea2d-a8fb-45d1-8085-698f51c6b6be', + }, + ], +}; + +export const clearOutputNotebook = { + paragraphs: [ + { + output: [], + input: { inputText: '%md\nhello', inputType: 'MARKDOWN' }, + dateCreated: '2023-12-18T23:38:50.848Z', + dateModified: '2023-12-18T23:39:12.265Z', + id: 'paragraph_de00ea2d-a8fb-45d1-8085-698f51c6b6be', + }, + ], +}; + +export const notebookPutResponse = { + status: 'OK', + message: { objectId: '69CpaYwBKIZhlDIhx-OK' }, +}; + +// ///////////////// /////////////////// /////////////////// SAMPLE NOTEBOOKS DEFAULTS /////////////////// /////////////////// /////////////////// + +export const emptyNotebook = { + path: 'sample-notebook-1', + dateCreated: '2023-12-14T18:49:43.375Z', + dateModified: '2023-12-15T06:13:23.463Z', + paragraphs: [], +}; // Sample notebook with all input and output export const sampleNotebook1 = { @@ -119,7 +178,8 @@ export const sampleParsedParagraghs1 = [ vizObjectInput: '{"viewMode":"view","panels":{"1":{"gridData":{"x":1,"y":0,"w":44,"h":20,"i":"1"},"type":"visualization","explicitInput":{"id":"1","savedObjectId":"935afa20-e0cd-11e7-9d07-1398ccfcefa3","vis":null}}},"isFullScreenMode":false,"filters":[],"useMargins":false,"id":"i3ccc6260-e314-11ea-9f99-b37e94bb02ca","timeRange":{"to":"2020-08-20T18:37:44.710Z","from":"2020-07-21T18:37:44.710Z"},"title":"embed_viz_i3ccc6260-e314-11ea-9f99-b37e94bb02ca","query":{"query":"","language":"lucene"},"refreshConfig":{"pause":true,"value":15}}', id: 3, - inp: '{"viewMode":"view","panels":{"1":{"gridData":{"x":1,"y":0,"w":44,"h":20,"i":"1"},"type":"visualization","explicitInput":{"id":"1","savedObjectId":"935afa20-e0cd-11e7-9d07-1398ccfcefa3","vis":null}}},"isFullScreenMode":false,"filters":[],"useMargins":false,"id":"i3ccc6260-e314-11ea-9f99-b37e94bb02ca","timeRange":{"to":"2020-08-20T18:37:44.710Z","from":"2020-07-21T18:37:44.710Z"},"title":"embed_viz_i3ccc6260-e314-11ea-9f99-b37e94bb02ca","query":{"query":"","language":"lucene"},"refreshConfig":{"pause":true,"value":15}}', + inp: + '{"viewMode":"view","panels":{"1":{"gridData":{"x":1,"y":0,"w":44,"h":20,"i":"1"},"type":"visualization","explicitInput":{"id":"1","savedObjectId":"935afa20-e0cd-11e7-9d07-1398ccfcefa3","vis":null}}},"isFullScreenMode":false,"filters":[],"useMargins":false,"id":"i3ccc6260-e314-11ea-9f99-b37e94bb02ca","timeRange":{"to":"2020-08-20T18:37:44.710Z","from":"2020-07-21T18:37:44.710Z"},"title":"embed_viz_i3ccc6260-e314-11ea-9f99-b37e94bb02ca","query":{"query":"","language":"lucene"},"refreshConfig":{"pause":true,"value":15}}', lang: 'text/x-', isInputExpanded: false, isOutputStale: false, @@ -160,6 +220,33 @@ export const sampleParsedParagraghs1 = [ }, ]; +export const sampleObservabilityVizParagraph = { + uniqueId: 'paragraph_6d3237a9-6486-4f93-aa25-0a1c838faahh', + isRunning: false, + inQueue: false, + isSelected: false, + isInputHidden: false, + isOutputHidden: false, + showAddPara: false, + isVizualisation: true, + vizObjectInput: + '{"viewMode":"view","panels":{"1":{"gridData":{"x":0,"y":0,"w":50,"h":20,"i":"1"},"type":"visualization","explicitInput":{"id":"1","savedObjectId":"xmcchIwB5xYbk0tldrMA"}}},"isFullScreenMode":false,"filters":[],"useMargins":false,"id":"id2ae6b50-aa91-11ee-b006-2351d419df89","timeRange":{"to":"2024-01-03T23:42:52.909Z","from":"2023-12-04T23:42:52.909Z"},"title":"embed_viz_id2ae6b50-aa91-11ee-b006-2351d419df89","query":{"query":"","language":"lucene"},"refreshConfig":{"pause":true,"value":15}}', + id: 3, + inp: + '{"viewMode":"view","panels":{"1":{"gridData":{"x":0,"y":0,"w":50,"h":20,"i":"1"},"type":"visualization","explicitInput":{"id":"1","savedObjectId":"xmcchIwB5xYbk0tldrMA"}}},"isFullScreenMode":false,"filters":[],"useMargins":false,"id":"id2ae6b50-aa91-11ee-b006-2351d419df89","timeRange":{"to":"2024-01-03T23:42:52.909Z","from":"2023-12-04T23:42:52.909Z"},"title":"embed_viz_id2ae6b50-aa91-11ee-b006-2351d419df89","query":{"query":"","language":"lucene"},"refreshConfig":{"pause":true,"value":15}}', + lang: 'text/x-', + isInputExpanded: false, + isOutputStale: false, + paraDivRef: undefined, + paraRef: undefined, + visEndTime: '2020-08-20T18:37:44.710Z', + visSavedObjId: '935afa20-e0cd-11e7-9d07-1398ccfcefa3', + visStartTime: '2020-07-21T18:37:44.710Z', + editorLanguage: '', + typeOut: ['OBSERVABILITY_VISUALIZATION'], + out: [''], +}; + // Sample notebook with all input and cleared outputs export const sampleNotebook2 = { id: 'note_5f4b9eed-5898-4b39-ba6c-755c0fadd84e', @@ -221,7 +308,8 @@ export const sampleParsedParagraghs2 = [ isVizualisation: false, vizObjectInput: '', id: 1, - inp: '# Type no output here\n* Sample link: [link](https://opensearch.org/)\n* ~~Strike~~, **Bold**, __Italic__', + inp: + '# Type no output here\n* Sample link: [link](https://opensearch.org/)\n* ~~Strike~~, **Bold**, __Italic__', lang: 'text/x-md', isInputExpanded: false, isOutputStale: false, @@ -272,7 +360,8 @@ export const sampleParsedParagraghs2 = [ vizObjectInput: '{"viewMode":"view","panels":{"1":{"gridData":{"x":1,"y":0,"w":44,"h":20,"i":"1"},"type":"visualization","explicitInput":{"id":"1","savedObjectId":"935afa20-e0cd-11e7-9d07-1398ccfcefa3","vis":null}}},"isFullScreenMode":false,"filters":[],"useMargins":false,"id":"i3ccc6260-e314-11ea-9f99-b37e94bb02ca","timeRange":{"to":"2020-08-20T18:37:44.710Z","from":"2020-07-21T18:37:44.710Z"},"title":"embed_viz_i3ccc6260-e314-11ea-9f99-b37e94bb02ca","query":{"query":"","language":"lucene"},"refreshConfig":{"pause":true,"value":15}}', id: 3, - inp: '{"viewMode":"view","panels":{"1":{"gridData":{"x":1,"y":0,"w":44,"h":20,"i":"1"},"type":"visualization","explicitInput":{"id":"1","savedObjectId":"935afa20-e0cd-11e7-9d07-1398ccfcefa3","vis":null}}},"isFullScreenMode":false,"filters":[],"useMargins":false,"id":"i3ccc6260-e314-11ea-9f99-b37e94bb02ca","timeRange":{"to":"2020-08-20T18:37:44.710Z","from":"2020-07-21T18:37:44.710Z"},"title":"embed_viz_i3ccc6260-e314-11ea-9f99-b37e94bb02ca","query":{"query":"","language":"lucene"},"refreshConfig":{"pause":true,"value":15}}', + inp: + '{"viewMode":"view","panels":{"1":{"gridData":{"x":1,"y":0,"w":44,"h":20,"i":"1"},"type":"visualization","explicitInput":{"id":"1","savedObjectId":"935afa20-e0cd-11e7-9d07-1398ccfcefa3","vis":null}}},"isFullScreenMode":false,"filters":[],"useMargins":false,"id":"i3ccc6260-e314-11ea-9f99-b37e94bb02ca","timeRange":{"to":"2020-08-20T18:37:44.710Z","from":"2020-07-21T18:37:44.710Z"},"title":"embed_viz_i3ccc6260-e314-11ea-9f99-b37e94bb02ca","query":{"query":"","language":"lucene"},"refreshConfig":{"pause":true,"value":15}}', lang: 'text/x-', isInputExpanded: false, isOutputStale: false,