From e9192a15191f70596cd575caf7644abf90354465 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 20 Mar 2020 16:17:01 -0700 Subject: [PATCH 1/9] Make JSON format of parameters field more prominent. Set default parameters to provide an example to users. - Move PAINLESS_LAB_KEY constant into context file, since that's the only place it's being used. --- .../application/components/output_pane/parameters_tab.tsx | 5 +---- .../plugins/painless_lab/public/application/constants.ts | 7 ------- x-pack/plugins/painless_lab/public/application/context.tsx | 3 ++- x-pack/plugins/painless_lab/public/application/store.ts | 6 +++++- 4 files changed, 8 insertions(+), 13 deletions(-) delete mode 100644 x-pack/plugins/painless_lab/public/application/constants.ts diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx index cc34b7a61735e..08c71f29e2079 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx @@ -35,7 +35,7 @@ export const ParametersTab: FunctionComponent = () => { {' '} @@ -51,9 +51,6 @@ export const ParametersTab: FunctionComponent = () => { } - helpText={i18n.translate('xpack.painlessLab.helpIconAriaLabel', { - defaultMessage: 'Use JSON format', - })} > Date: Fri, 20 Mar 2020 16:27:44 -0700 Subject: [PATCH 2/9] Set default document to provide an example to users. --- x-pack/plugins/painless_lab/public/application/store.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/painless_lab/public/application/store.ts b/x-pack/plugins/painless_lab/public/application/store.ts index a7ad8f30443a6..a602914e79aec 100644 --- a/x-pack/plugins/painless_lab/public/application/store.ts +++ b/x-pack/plugins/painless_lab/public/application/store.ts @@ -22,7 +22,9 @@ export const initialState = { "number-parameter": 1.5, "boolean-parameter": true }`, - index: '', - document: '', + index: 'default-index', + document: `{ + "my-field": "field-value" +}`, query: '', }; From 6d48b0305dd2097ce58d163a2d2200dd02078936 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 20 Mar 2020 16:33:53 -0700 Subject: [PATCH 3/9] Simplify context's updateState interface. --- .../painless_lab/public/application/components/main.tsx | 4 ++-- .../application/components/output_pane/context_tab.tsx | 8 ++++---- .../application/components/output_pane/parameters_tab.tsx | 2 +- .../plugins/painless_lab/public/application/context.tsx | 7 +++---- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/painless_lab/public/application/components/main.tsx b/x-pack/plugins/painless_lab/public/application/components/main.tsx index d692eab27ff42..e79b1fc7ec8e7 100644 --- a/x-pack/plugins/painless_lab/public/application/components/main.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/main.tsx @@ -64,7 +64,7 @@ export const Main = () => { updateState(() => ({ code: nextCode }))} + onChange={nextCode => updateState({ code: nextCode })} /> @@ -78,8 +78,8 @@ export const Main = () => { isLoading={inProgress} toggleRequestFlyout={toggleRequestFlyout} isRequestFlyoutOpen={isRequestFlyoutOpen} - reset={() => updateState(() => ({ code: exampleScript }))} isNavDrawerLocked={isNavDrawerLocked} + reset={() => updateState({ code: exampleScript })} /> {isRequestFlyoutOpen && ( diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx index 77732ea1ca6ce..c19e1f30de173 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx @@ -60,7 +60,7 @@ export const ContextTab: FunctionComponent = () => { updateState(() => ({ context: nextContext }))} + onChange={nextContext => updateState({ context: nextContext })} itemLayoutAlign="top" hasDividers fullWidth @@ -89,7 +89,7 @@ export const ContextTab: FunctionComponent = () => { value={index || ''} onChange={e => { const nextIndex = e.target.value; - updateState(() => ({ index: nextIndex })); + updateState({ index: nextIndex }); }} /> @@ -126,7 +126,7 @@ export const ContextTab: FunctionComponent = () => { languageId="json" height={150} value={query} - onChange={nextQuery => updateState(() => ({ query: nextQuery }))} + onChange={nextQuery => updateState({ query: nextQuery })} options={{ fontSize: 12, minimap: { @@ -165,7 +165,7 @@ export const ContextTab: FunctionComponent = () => { languageId="json" height={400} value={document} - onChange={nextDocument => updateState(() => ({ document: nextDocument }))} + onChange={nextDocument => updateState({ document: nextDocument })} options={{ fontSize: 12, minimap: { diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx index 08c71f29e2079..dacfa86a7ec9b 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx @@ -57,7 +57,7 @@ export const ParametersTab: FunctionComponent = () => { languageId="json" height={600} value={state.parameters} - onChange={nextParams => updateState(() => ({ parameters: nextParams }))} + onChange={nextParams => updateState({ parameters: nextParams })} options={{ fontSize: 12, minimap: { diff --git a/x-pack/plugins/painless_lab/public/application/context.tsx b/x-pack/plugins/painless_lab/public/application/context.tsx index 012def0dba16d..566d282ced59d 100644 --- a/x-pack/plugins/painless_lab/public/application/context.tsx +++ b/x-pack/plugins/painless_lab/public/application/context.tsx @@ -15,7 +15,7 @@ const PAINLESS_LAB_KEY = 'painlessLabState'; interface ContextValue { state: Store; - updateState: (nextState: (s: Store) => Partial) => void; + updateState: (changes: Partial) => void; services: { http: HttpSetup; }; @@ -41,11 +41,10 @@ export const AppContextProvider = ({ ...JSON.parse(localStorage.getItem(PAINLESS_LAB_KEY) || '{}'), })); - const updateState = (getNextState: (s: Store) => Partial): void => { - const update = getNextState(state); + const updateState = (changes: Partial): void => { const nextState = { ...state, - ...update, + ...changes, }; localStorage.setItem(PAINLESS_LAB_KEY, JSON.stringify(nextState)); setState(() => nextState); From 7b4bbe56f037ae61cd13fab5e876405385b0b510 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 20 Mar 2020 16:58:50 -0700 Subject: [PATCH 4/9] Refactor store and context file organization. - Remove common directory, move constants and types files to root. - Move initialState into context file, where it's being used. --- .../public/application/components/main.tsx | 6 ++-- .../components/output_pane/context_tab.tsx | 2 +- .../components/output_pane/output_pane.tsx | 2 +- .../components/output_pane/output_tab.tsx | 2 +- .../application/{common => }/constants.tsx | 0 .../public/application/context.tsx | 33 ++++++++++++++----- .../application/hooks/use_submit_code.ts | 3 +- .../public/application/lib/format.test.ts | 2 +- .../public/application/lib/format.ts | 3 +- .../painless_lab/public/application/store.ts | 30 ----------------- .../public/application/{common => }/types.ts | 9 +++++ .../painless_lab/server/routes/api/execute.ts | 2 +- 12 files changed, 43 insertions(+), 51 deletions(-) rename x-pack/plugins/painless_lab/public/application/{common => }/constants.tsx (100%) delete mode 100644 x-pack/plugins/painless_lab/public/application/store.ts rename x-pack/plugins/painless_lab/public/application/{common => }/types.ts (85%) diff --git a/x-pack/plugins/painless_lab/public/application/components/main.tsx b/x-pack/plugins/painless_lab/public/application/components/main.tsx index e79b1fc7ec8e7..897b31bdb8408 100644 --- a/x-pack/plugins/painless_lab/public/application/components/main.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/main.tsx @@ -8,14 +8,14 @@ import React, { useState, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { formatRequestPayload, formatJson } from '../lib/format'; -import { exampleScript } from '../common/constants'; -import { PayloadFormat } from '../common/types'; +import { exampleScript } from '../constants'; +import { PayloadFormat } from '../types'; import { useSubmitCode } from '../hooks'; +import { useAppContext } from '../context'; import { OutputPane } from './output_pane'; import { MainControls } from './main_controls'; import { Editor } from './editor'; import { RequestFlyout } from './request_flyout'; -import { useAppContext } from '../context'; export const Main = () => { const { diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx index c19e1f30de173..f1adf85d8f605 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx @@ -20,7 +20,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { CodeEditor } from '../../../../../../../src/plugins/kibana_react/public'; -import { painlessContextOptions } from '../../common/constants'; +import { painlessContextOptions } from '../../constants'; import { useAppContext } from '../../context'; export const ContextTab: FunctionComponent = () => { diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx index 156363a1c89a8..e6a97bb02f738 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx @@ -15,7 +15,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Response } from '../../common/types'; +import { Response } from '../../types'; import { OutputTab } from './output_tab'; import { ParametersTab } from './parameters_tab'; import { ContextTab } from './context_tab'; diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/output_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/output_tab.tsx index fafd0f1f7cde4..8969e5421640a 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/output_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/output_tab.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiCodeBlock, EuiSpacer } from '@elastic/eui'; import { formatResponse } from '../../lib/format'; -import { Response } from '../../common/types'; +import { Response } from '../../types'; interface Props { response?: Response; diff --git a/x-pack/plugins/painless_lab/public/application/common/constants.tsx b/x-pack/plugins/painless_lab/public/application/constants.tsx similarity index 100% rename from x-pack/plugins/painless_lab/public/application/common/constants.tsx rename to x-pack/plugins/painless_lab/public/application/constants.tsx diff --git a/x-pack/plugins/painless_lab/public/application/context.tsx b/x-pack/plugins/painless_lab/public/application/context.tsx index 566d282ced59d..7ad60d4bcbf37 100644 --- a/x-pack/plugins/painless_lab/public/application/context.tsx +++ b/x-pack/plugins/painless_lab/public/application/context.tsx @@ -8,11 +8,34 @@ import React, { createContext, ReactNode, useState, useContext } from 'react'; import { HttpSetup } from 'src/core/public'; import { Links } from '../links'; +import { exampleScript, painlessContextOptions } from './constants'; +import { Store } from './types'; -import { initialState, Store } from './store'; +interface AppContextProviderArgs { + children: ReactNode; + value: { + http: HttpSetup; + links: Links; + }; +} const PAINLESS_LAB_KEY = 'painlessLabState'; +const initialState = { + context: painlessContextOptions[0].value, + code: exampleScript, + parameters: `{ + "string-parameter": "yay", + "number-parameter": 1.5, + "boolean-parameter": true +}`, + index: 'default-index', + document: `{ + "my-field": "field-value" +}`, + query: '', +}; + interface ContextValue { state: Store; updateState: (changes: Partial) => void; @@ -24,14 +47,6 @@ interface ContextValue { const AppContext = createContext(undefined as any); -interface AppContextProviderArgs { - children: ReactNode; - value: { - http: HttpSetup; - links: Links; - }; -} - export const AppContextProvider = ({ children, value: { http, links, chrome }, diff --git a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts index ead5c2be34d99..722696ba87d88 100644 --- a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts +++ b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts @@ -9,9 +9,8 @@ import { HttpSetup } from 'kibana/public'; import { debounce } from 'lodash'; import { API_BASE_PATH } from '../../../common/constants'; -import { Response, PayloadFormat } from '../common/types'; +import { Response, PayloadFormat, Store } from '../types'; import { formatRequestPayload } from '../lib/format'; -import { Store } from '../store'; const DEBOUNCE_MS = 800; diff --git a/x-pack/plugins/painless_lab/public/application/lib/format.test.ts b/x-pack/plugins/painless_lab/public/application/lib/format.test.ts index 1f46d6e665bcc..5f0022ebbc089 100644 --- a/x-pack/plugins/painless_lab/public/application/lib/format.test.ts +++ b/x-pack/plugins/painless_lab/public/application/lib/format.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PayloadFormat } from '../common/types'; +import { PayloadFormat } from '../types'; import { formatRequestPayload } from './format'; describe('formatRequestPayload', () => { diff --git a/x-pack/plugins/painless_lab/public/application/lib/format.ts b/x-pack/plugins/painless_lab/public/application/lib/format.ts index cf719a68380f0..f0b57b1be472a 100644 --- a/x-pack/plugins/painless_lab/public/application/lib/format.ts +++ b/x-pack/plugins/painless_lab/public/application/lib/format.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Response, ExecutionError, PayloadFormat } from '../common/types'; -import { Store } from '../store'; +import { Response, ExecutionError, PayloadFormat, Store } from '../types'; function prettifyPayload(payload = '', indentationLevel = 0) { const indentation = new Array(indentationLevel + 1).join(' '); diff --git a/x-pack/plugins/painless_lab/public/application/store.ts b/x-pack/plugins/painless_lab/public/application/store.ts deleted file mode 100644 index a602914e79aec..0000000000000 --- a/x-pack/plugins/painless_lab/public/application/store.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { exampleScript, painlessContextOptions } from './common/constants'; - -export interface Store { - context: string; - code: string; - parameters: string; - index: string; - document: string; - query: string; -} - -export const initialState = { - context: painlessContextOptions[0].value, - code: exampleScript, - parameters: `{ - "string-parameter": "yay", - "number-parameter": 1.5, - "boolean-parameter": true -}`, - index: 'default-index', - document: `{ - "my-field": "field-value" -}`, - query: '', -}; diff --git a/x-pack/plugins/painless_lab/public/application/common/types.ts b/x-pack/plugins/painless_lab/public/application/types.ts similarity index 85% rename from x-pack/plugins/painless_lab/public/application/common/types.ts rename to x-pack/plugins/painless_lab/public/application/types.ts index e0c7a8c7a6ff3..96ccdac73a245 100644 --- a/x-pack/plugins/painless_lab/public/application/common/types.ts +++ b/x-pack/plugins/painless_lab/public/application/types.ts @@ -4,6 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +export interface Store { + context: string; + code: string; + parameters: string; + index: string; + document: string; + query: string; +} + // This should be an enumerated list export type Context = string; diff --git a/x-pack/plugins/painless_lab/server/routes/api/execute.ts b/x-pack/plugins/painless_lab/server/routes/api/execute.ts index 559d02aa08386..55adb5e0410cc 100644 --- a/x-pack/plugins/painless_lab/server/routes/api/execute.ts +++ b/x-pack/plugins/painless_lab/server/routes/api/execute.ts @@ -5,8 +5,8 @@ */ import { schema } from '@kbn/config-schema'; -import { RouteDependencies } from '../../types'; import { API_BASE_PATH } from '../../../common/constants'; +import { RouteDependencies } from '../../types'; import { isEsError } from '../../lib'; const bodySchema = schema.string(); From 2647918e6bdac1f41ea2edbae4ddf5a847c88c5d Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 20 Mar 2020 17:42:52 -0700 Subject: [PATCH 5/9] Add validation for index input. - This required a refactor to the shape of our store. --- .../public/application/components/main.tsx | 23 ++++--- .../components/output_pane/context_tab.tsx | 37 ++++++++--- .../components/output_pane/parameters_tab.tsx | 10 ++- .../public/application/context.tsx | 66 +++++++++++++------ .../application/hooks/use_submit_code.ts | 4 +- .../public/application/lib/format.ts | 4 +- .../painless_lab/public/application/types.ts | 14 +++- 7 files changed, 107 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/painless_lab/public/application/components/main.tsx b/x-pack/plugins/painless_lab/public/application/components/main.tsx index 897b31bdb8408..10907536e9cc2 100644 --- a/x-pack/plugins/painless_lab/public/application/components/main.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/main.tsx @@ -17,10 +17,10 @@ import { MainControls } from './main_controls'; import { Editor } from './editor'; import { RequestFlyout } from './request_flyout'; -export const Main = () => { +export const Main: React.FunctionComponent = () => { const { - state, - updateState, + store: { payload, validation }, + updatePayload, services: { http, chrome: { getIsNavDrawerLocked$ }, @@ -31,10 +31,12 @@ export const Main = () => { const [isRequestFlyoutOpen, setRequestFlyoutOpen] = useState(false); const { inProgress, response, submit } = useSubmitCode(http); - // Live-update the output and persist state as the user changes it. + // Live-update the output and persist payload state as the user changes it. useEffect(() => { - submit(state); - }, [state, submit]); + if (validation.isValid) { + submit(payload); + } + }, [payload, submit, validation.isValid]); const toggleRequestFlyout = () => { setRequestFlyoutOpen(!isRequestFlyoutOpen); @@ -62,10 +64,7 @@ export const Main = () => { - updateState({ code: nextCode })} - /> + updatePayload({ code: nextCode })} /> @@ -79,14 +78,14 @@ export const Main = () => { toggleRequestFlyout={toggleRequestFlyout} isRequestFlyoutOpen={isRequestFlyoutOpen} isNavDrawerLocked={isNavDrawerLocked} - reset={() => updateState({ code: exampleScript })} + reset={() => updatePayload({ code: exampleScript })} /> {isRequestFlyoutOpen && ( setRequestFlyoutOpen(false)} - requestBody={formatRequestPayload(state, PayloadFormat.PRETTY)} + requestBody={formatRequestPayload(payload, PayloadFormat.PRETTY)} response={response ? formatJson(response.result || response.error) : ''} /> )} diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx index f1adf85d8f605..47efd524f092a 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx @@ -24,8 +24,12 @@ import { painlessContextOptions } from '../../constants'; import { useAppContext } from '../../context'; export const ContextTab: FunctionComponent = () => { - const { state, updateState, links } = useAppContext(); - const { context, document, index, query } = state; + const { + store: { payload, validation }, + updatePayload, + links, + } = useAppContext(); + const { context, document, index, query } = payload; return ( <> @@ -60,7 +64,7 @@ export const ContextTab: FunctionComponent = () => { updateState({ context: nextContext })} + onChange={nextContext => updatePayload({ context: nextContext })} itemLayoutAlign="top" hasDividers fullWidth @@ -72,25 +76,38 @@ export const ContextTab: FunctionComponent = () => { label={ - {' '} + {' '} } fullWidth + isInvalid={!validation.fields.index} + error={ + validation.fields.index + ? [] + : [ + i18n.translate('xpack.painlessLab.indexFieldMissingErrorMessage', { + defaultMessage: 'Enter an index name', + }), + ] + } > { const nextIndex = e.target.value; - updateState({ index: nextIndex }); + updatePayload({ index: nextIndex }); }} + isInvalid={!validation.fields.index} /> )} @@ -126,7 +143,7 @@ export const ContextTab: FunctionComponent = () => { languageId="json" height={150} value={query} - onChange={nextQuery => updateState({ query: nextQuery })} + onChange={nextQuery => updatePayload({ query: nextQuery })} options={{ fontSize: 12, minimap: { @@ -152,7 +169,7 @@ export const ContextTab: FunctionComponent = () => { {' '} @@ -165,7 +182,7 @@ export const ContextTab: FunctionComponent = () => { languageId="json" height={400} value={document} - onChange={nextDocument => updateState({ document: nextDocument })} + onChange={nextDocument => updatePayload({ document: nextDocument })} options={{ fontSize: 12, minimap: { diff --git a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx index dacfa86a7ec9b..7c8bce0f7b21b 100644 --- a/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx @@ -21,7 +21,11 @@ import { CodeEditor } from '../../../../../../../src/plugins/kibana_react/public import { useAppContext } from '../../context'; export const ParametersTab: FunctionComponent = () => { - const { state, updateState, links } = useAppContext(); + const { + store: { payload }, + updatePayload, + links, + } = useAppContext(); return ( <> @@ -56,8 +60,8 @@ export const ParametersTab: FunctionComponent = () => { updateState({ parameters: nextParams })} + value={payload.parameters} + onChange={nextParams => updatePayload({ parameters: nextParams })} options={{ fontSize: 12, minimap: { diff --git a/x-pack/plugins/painless_lab/public/application/context.tsx b/x-pack/plugins/painless_lab/public/application/context.tsx index 7ad60d4bcbf37..90ef01174c50c 100644 --- a/x-pack/plugins/painless_lab/public/application/context.tsx +++ b/x-pack/plugins/painless_lab/public/application/context.tsx @@ -9,7 +9,7 @@ import { HttpSetup } from 'src/core/public'; import { Links } from '../links'; import { exampleScript, painlessContextOptions } from './constants'; -import { Store } from './types'; +import { Store, Payload, Validation } from './types'; interface AppContextProviderArgs { children: ReactNode; @@ -19,9 +19,16 @@ interface AppContextProviderArgs { }; } -const PAINLESS_LAB_KEY = 'painlessLabState'; +interface ContextValue { + store: Store; + updatePayload: (changes: Partial) => void; + services: { + http: HttpSetup; + }; + links: Links; +} -const initialState = { +const initialPayload = { context: painlessContextOptions[0].value, code: exampleScript, parameters: `{ @@ -36,37 +43,54 @@ const initialState = { query: '', }; -interface ContextValue { - state: Store; - updateState: (changes: Partial) => void; - services: { - http: HttpSetup; - }; - links: Links; -} - const AppContext = createContext(undefined as any); +const validatePayload = (payload: Payload): Validation => { + const { index } = payload; + + // For now just validate that the user has entered an index. + const indexExists = Boolean(index || index.trim()); + + return { + isValid: indexExists, + fields: { + index: indexExists, + }, + }; +}; + export const AppContextProvider = ({ children, value: { http, links, chrome }, }: AppContextProviderArgs) => { - const [state, setState] = useState(() => ({ - ...initialState, + const PAINLESS_LAB_KEY = 'painlessLabState'; + + const defaultPayload = { + ...initialPayload, ...JSON.parse(localStorage.getItem(PAINLESS_LAB_KEY) || '{}'), - })); + }; - const updateState = (changes: Partial): void => { - const nextState = { - ...state, + const [store, setStore] = useState({ + payload: defaultPayload, + validation: validatePayload(defaultPayload), + }); + + const updatePayload = (changes: Partial): void => { + const nextPayload = { + ...store.payload, ...changes, }; - localStorage.setItem(PAINLESS_LAB_KEY, JSON.stringify(nextState)); - setState(() => nextState); + // Persist state locally so we can load it up when the user reopens the app. + localStorage.setItem(PAINLESS_LAB_KEY, JSON.stringify(nextPayload)); + + setStore({ + payload: nextPayload, + validation: validatePayload(nextPayload), + }); }; return ( - + {children} ); diff --git a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts index 722696ba87d88..36cd4f280ac4c 100644 --- a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts +++ b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts @@ -9,7 +9,7 @@ import { HttpSetup } from 'kibana/public'; import { debounce } from 'lodash'; import { API_BASE_PATH } from '../../../common/constants'; -import { Response, PayloadFormat, Store } from '../types'; +import { Response, PayloadFormat, Payload } from '../types'; import { formatRequestPayload } from '../lib/format'; const DEBOUNCE_MS = 800; @@ -21,7 +21,7 @@ export const useSubmitCode = (http: HttpSetup) => { const submit = useCallback( debounce( - async (config: Store) => { + async (config: Payload) => { setInProgress(true); // Prevent an older request that resolves after a more recent request from clobbering it. diff --git a/x-pack/plugins/painless_lab/public/application/lib/format.ts b/x-pack/plugins/painless_lab/public/application/lib/format.ts index f0b57b1be472a..15ecdf682d247 100644 --- a/x-pack/plugins/painless_lab/public/application/lib/format.ts +++ b/x-pack/plugins/painless_lab/public/application/lib/format.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Response, ExecutionError, PayloadFormat, Store } from '../types'; +import { Response, ExecutionError, PayloadFormat, Payload } from '../types'; function prettifyPayload(payload = '', indentationLevel = 0) { const indentation = new Array(indentationLevel + 1).join(' '); @@ -16,7 +16,7 @@ function prettifyPayload(payload = '', indentationLevel = 0) { * e.g. 1.0, is preserved instead of being coerced to an integer, e.g. 1. */ export function formatRequestPayload( - { code, context, parameters, index, document, query }: Partial, + { code, context, parameters, index, document, query }: Partial, format: PayloadFormat = PayloadFormat.UGLY ): string { const isAdvancedContext = context === 'filter' || context === 'score'; diff --git a/x-pack/plugins/painless_lab/public/application/types.ts b/x-pack/plugins/painless_lab/public/application/types.ts index 96ccdac73a245..d800558ef7ecc 100644 --- a/x-pack/plugins/painless_lab/public/application/types.ts +++ b/x-pack/plugins/painless_lab/public/application/types.ts @@ -5,6 +5,11 @@ */ export interface Store { + payload: Payload; + validation: Validation; +} + +export interface Payload { context: string; code: string; parameters: string; @@ -13,7 +18,14 @@ export interface Store { query: string; } -// This should be an enumerated list +export interface Validation { + isValid: boolean; + fields: { + index: boolean; + }; +} + +// TODO: This should be an enumerated list export type Context = string; export enum PayloadFormat { From be255b88ac83b4e7c8a1cd2789dea8831eb32500 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 23 Mar 2020 12:27:35 -0700 Subject: [PATCH 6/9] Create context directory. --- .../application/{ => context}/context.tsx | 21 +++--------------- .../public/application/context/index.tsx | 7 ++++++ .../application/context/initial_payload.ts | 22 +++++++++++++++++++ .../painless_lab/public/application/index.tsx | 1 - 4 files changed, 32 insertions(+), 19 deletions(-) rename x-pack/plugins/painless_lab/public/application/{ => context}/context.tsx (83%) create mode 100644 x-pack/plugins/painless_lab/public/application/context/index.tsx create mode 100644 x-pack/plugins/painless_lab/public/application/context/initial_payload.ts diff --git a/x-pack/plugins/painless_lab/public/application/context.tsx b/x-pack/plugins/painless_lab/public/application/context/context.tsx similarity index 83% rename from x-pack/plugins/painless_lab/public/application/context.tsx rename to x-pack/plugins/painless_lab/public/application/context/context.tsx index 90ef01174c50c..4da6c6770bf16 100644 --- a/x-pack/plugins/painless_lab/public/application/context.tsx +++ b/x-pack/plugins/painless_lab/public/application/context/context.tsx @@ -7,9 +7,9 @@ import React, { createContext, ReactNode, useState, useContext } from 'react'; import { HttpSetup } from 'src/core/public'; -import { Links } from '../links'; -import { exampleScript, painlessContextOptions } from './constants'; -import { Store, Payload, Validation } from './types'; +import { Links } from '../../links'; +import { Store, Payload, Validation } from '../types'; +import { initialPayload } from './initial_payload'; interface AppContextProviderArgs { children: ReactNode; @@ -28,21 +28,6 @@ interface ContextValue { links: Links; } -const initialPayload = { - context: painlessContextOptions[0].value, - code: exampleScript, - parameters: `{ - "string-parameter": "yay", - "number-parameter": 1.5, - "boolean-parameter": true -}`, - index: 'default-index', - document: `{ - "my-field": "field-value" -}`, - query: '', -}; - const AppContext = createContext(undefined as any); const validatePayload = (payload: Payload): Validation => { diff --git a/x-pack/plugins/painless_lab/public/application/context/index.tsx b/x-pack/plugins/painless_lab/public/application/context/index.tsx new file mode 100644 index 0000000000000..59b43c7e86cb9 --- /dev/null +++ b/x-pack/plugins/painless_lab/public/application/context/index.tsx @@ -0,0 +1,7 @@ +/* + * 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 { AppContextProvider } from './context'; diff --git a/x-pack/plugins/painless_lab/public/application/context/initial_payload.ts b/x-pack/plugins/painless_lab/public/application/context/initial_payload.ts new file mode 100644 index 0000000000000..b14554880f7cc --- /dev/null +++ b/x-pack/plugins/painless_lab/public/application/context/initial_payload.ts @@ -0,0 +1,22 @@ +/* + * 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 { exampleScript, painlessContextOptions } from '../constants'; + +export const initialPayload = { + context: painlessContextOptions[0].value, + code: exampleScript, + parameters: `{ + "string-parameter": "yay", + "number-parameter": 1.5, + "boolean-parameter": true +}`, + index: 'default-index', + document: `{ + "my-field": "field-value" +}`, + query: '', +}; diff --git a/x-pack/plugins/painless_lab/public/application/index.tsx b/x-pack/plugins/painless_lab/public/application/index.tsx index f0a0280d12457..0c9a6f922e7aa 100644 --- a/x-pack/plugins/painless_lab/public/application/index.tsx +++ b/x-pack/plugins/painless_lab/public/application/index.tsx @@ -10,7 +10,6 @@ import { CoreSetup, CoreStart } from 'kibana/public'; import { createKibanaReactContext } from '../../../../../src/plugins/kibana_react/public'; import { Links } from '../links'; - import { AppContextProvider } from './context'; import { Main } from './components/main'; From 6daffe2058fc8941e750b6b85c2f9e997a407836 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 23 Mar 2020 12:40:50 -0700 Subject: [PATCH 7/9] Use useState callback. --- .../public/application/context/context.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/painless_lab/public/application/context/context.tsx b/x-pack/plugins/painless_lab/public/application/context/context.tsx index 4da6c6770bf16..b97344532822e 100644 --- a/x-pack/plugins/painless_lab/public/application/context/context.tsx +++ b/x-pack/plugins/painless_lab/public/application/context/context.tsx @@ -50,14 +50,17 @@ export const AppContextProvider = ({ }: AppContextProviderArgs) => { const PAINLESS_LAB_KEY = 'painlessLabState'; - const defaultPayload = { - ...initialPayload, - ...JSON.parse(localStorage.getItem(PAINLESS_LAB_KEY) || '{}'), - }; + const [store, setStore] = useState(() => { + // Using a callback here ensures these values are only calculated on the first render. + const defaultPayload = { + ...initialPayload, + ...JSON.parse(localStorage.getItem(PAINLESS_LAB_KEY) || '{}'), + }; - const [store, setStore] = useState({ - payload: defaultPayload, - validation: validatePayload(defaultPayload), + return { + payload: defaultPayload, + validation: validatePayload(defaultPayload), + }; }); const updatePayload = (changes: Partial): void => { From 69631a4369cf460fd332508e988be34ba077a6ae Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 23 Mar 2020 12:48:44 -0700 Subject: [PATCH 8/9] Use example values that aren't so goofy. --- .../painless_lab/public/application/context/index.tsx | 2 +- .../public/application/context/initial_payload.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/painless_lab/public/application/context/index.tsx b/x-pack/plugins/painless_lab/public/application/context/index.tsx index 59b43c7e86cb9..7a685137b7a4f 100644 --- a/x-pack/plugins/painless_lab/public/application/context/index.tsx +++ b/x-pack/plugins/painless_lab/public/application/context/index.tsx @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { AppContextProvider } from './context'; +export { AppContextProvider, useAppContext } from './context'; diff --git a/x-pack/plugins/painless_lab/public/application/context/initial_payload.ts b/x-pack/plugins/painless_lab/public/application/context/initial_payload.ts index b14554880f7cc..4d9d8ad8b3ae7 100644 --- a/x-pack/plugins/painless_lab/public/application/context/initial_payload.ts +++ b/x-pack/plugins/painless_lab/public/application/context/initial_payload.ts @@ -10,13 +10,13 @@ export const initialPayload = { context: painlessContextOptions[0].value, code: exampleScript, parameters: `{ - "string-parameter": "yay", - "number-parameter": 1.5, - "boolean-parameter": true + "string_parameter": "string value", + "number_parameter": 1.5, + "boolean_parameter": true }`, - index: 'default-index', + index: 'my-index', document: `{ - "my-field": "field-value" + "my_field": "field_value" }`, query: '', }; From 87a7f587d4f1cd9ded959e0816716ecfeb27e049 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 23 Mar 2020 13:04:49 -0700 Subject: [PATCH 9/9] Fix TS bugs. --- .../painless_lab/public/application/context/context.tsx | 4 +++- x-pack/plugins/painless_lab/public/application/index.tsx | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/painless_lab/public/application/context/context.tsx b/x-pack/plugins/painless_lab/public/application/context/context.tsx index b97344532822e..0fb5842dfea58 100644 --- a/x-pack/plugins/painless_lab/public/application/context/context.tsx +++ b/x-pack/plugins/painless_lab/public/application/context/context.tsx @@ -5,7 +5,7 @@ */ import React, { createContext, ReactNode, useState, useContext } from 'react'; -import { HttpSetup } from 'src/core/public'; +import { HttpSetup, ChromeStart } from 'src/core/public'; import { Links } from '../../links'; import { Store, Payload, Validation } from '../types'; @@ -16,6 +16,7 @@ interface AppContextProviderArgs { value: { http: HttpSetup; links: Links; + chrome: ChromeStart; }; } @@ -24,6 +25,7 @@ interface ContextValue { updatePayload: (changes: Partial) => void; services: { http: HttpSetup; + chrome: ChromeStart; }; links: Links; } diff --git a/x-pack/plugins/painless_lab/public/application/index.tsx b/x-pack/plugins/painless_lab/public/application/index.tsx index 0c9a6f922e7aa..ebcb84bbce83c 100644 --- a/x-pack/plugins/painless_lab/public/application/index.tsx +++ b/x-pack/plugins/painless_lab/public/application/index.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { CoreSetup, CoreStart } from 'kibana/public'; +import { HttpSetup, ChromeStart } from 'src/core/public'; import { createKibanaReactContext } from '../../../../../src/plugins/kibana_react/public'; import { Links } from '../links'; @@ -14,11 +15,11 @@ import { AppContextProvider } from './context'; import { Main } from './components/main'; interface AppDependencies { - http: CoreSetup['http']; + http: HttpSetup; I18nContext: CoreStart['i18n']['Context']; uiSettings: CoreSetup['uiSettings']; links: Links; - chrome: CoreSetup['chrome']; + chrome: ChromeStart; } export function renderApp(