-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
- Loading branch information
1 parent
0c57aa8
commit 84a7642
Showing
10 changed files
with
543 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.auaActionWizard__selectedActionFactoryContainer { | ||
background-color: $euiColorLightestShade; | ||
padding: $euiSize; | ||
} | ||
|
||
.auaActionWizard__actionFactoryItem { | ||
.euiKeyPadMenuItem__label { | ||
height: #{$euiSizeXL}; | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.story.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import React from 'react'; | ||
import { storiesOf } from '@storybook/react'; | ||
import { dashboardDrilldownActionFactory, Demo, urlDrilldownActionFactory } from './test_data'; | ||
|
||
storiesOf('components/ActionWizard', module) | ||
.add('default', () => ( | ||
<Demo actionFactories={[dashboardDrilldownActionFactory, urlDrilldownActionFactory]} /> | ||
)) | ||
.add('Only one factory is available', () => ( | ||
// to make sure layout doesn't break | ||
<Demo actionFactories={[dashboardDrilldownActionFactory]} /> | ||
)) | ||
.add('Long list of action factories', () => ( | ||
// to make sure layout doesn't break | ||
<Demo | ||
actionFactories={[ | ||
dashboardDrilldownActionFactory, | ||
urlDrilldownActionFactory, | ||
dashboardDrilldownActionFactory, | ||
urlDrilldownActionFactory, | ||
dashboardDrilldownActionFactory, | ||
urlDrilldownActionFactory, | ||
dashboardDrilldownActionFactory, | ||
urlDrilldownActionFactory, | ||
]} | ||
/> | ||
)); |
64 changes: 64 additions & 0 deletions
64
x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import React from 'react'; | ||
import { cleanup, fireEvent, render } from '@testing-library/react/pure'; | ||
import '@testing-library/jest-dom/extend-expect'; // TODO: this should be global | ||
import { TEST_SUBJ_ACTION_FACTORY_ITEM, TEST_SUBJ_SELECTED_ACTION_FACTORY } from './action_wizard'; | ||
import { | ||
dashboardDrilldownActionFactory, | ||
dashboards, | ||
Demo, | ||
urlDrilldownActionFactory, | ||
} from './test_data'; | ||
|
||
// TODO: afterEach is not available for it globally during setup | ||
// https://github.com/elastic/kibana/issues/59469 | ||
afterEach(cleanup); | ||
|
||
test('Pick and configure action', () => { | ||
const screen = render( | ||
<Demo actionFactories={[dashboardDrilldownActionFactory, urlDrilldownActionFactory]} /> | ||
); | ||
|
||
// check that all factories are displayed to pick | ||
expect(screen.getAllByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).toHaveLength(2); | ||
|
||
// select URL one | ||
fireEvent.click(screen.getByText(/Go to URL/i)); | ||
|
||
// Input url | ||
const URL = 'https://elastic.co'; | ||
fireEvent.change(screen.getByLabelText(/url/i), { | ||
target: { value: URL }, | ||
}); | ||
|
||
// change to dashboard | ||
fireEvent.click(screen.getByText(/change/i)); | ||
fireEvent.click(screen.getByText(/Go to Dashboard/i)); | ||
|
||
// Select dashboard | ||
fireEvent.change(screen.getByLabelText(/Choose destination dashboard/i), { | ||
target: { value: dashboards[1].id }, | ||
}); | ||
}); | ||
|
||
test('If only one actions factory is available then actionFactory selection is emitted without user input', () => { | ||
const screen = render(<Demo actionFactories={[urlDrilldownActionFactory]} />); | ||
|
||
// check that no factories are displayed to pick from | ||
expect(screen.queryByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).not.toBeInTheDocument(); | ||
expect(screen.queryByTestId(TEST_SUBJ_SELECTED_ACTION_FACTORY)).toBeInTheDocument(); | ||
|
||
// Input url | ||
const URL = 'https://elastic.co'; | ||
fireEvent.change(screen.getByLabelText(/url/i), { | ||
target: { value: URL }, | ||
}); | ||
|
||
// check that can't change to action factory type | ||
expect(screen.queryByTestId(/change/i)).not.toBeInTheDocument(); | ||
}); |
196 changes: 196 additions & 0 deletions
196
x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import React from 'react'; | ||
import { | ||
EuiButtonEmpty, | ||
EuiFlexGroup, | ||
EuiFlexItem, | ||
EuiIcon, | ||
EuiSpacer, | ||
EuiText, | ||
EuiKeyPadMenuItemButton, | ||
} from '@elastic/eui'; | ||
import { txtChangeButton } from './i18n'; | ||
import './action_wizard.scss'; | ||
|
||
// TODO: this interface is temporary for just moving forward with the component | ||
// and it will be imported from the ../ui_actions when implemented properly | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
export type ActionBaseConfig = {}; | ||
export interface ActionFactory<Config extends ActionBaseConfig = ActionBaseConfig> { | ||
type: string; // TODO: type should be tied to Action and ActionByType | ||
displayName: string; | ||
iconType?: string; | ||
wizard: React.FC<ActionFactoryWizardProps<Config>>; | ||
createConfig: () => Config; | ||
isValid: (config: Config) => boolean; | ||
} | ||
|
||
export interface ActionFactoryWizardProps<Config extends ActionBaseConfig> { | ||
config?: Config; | ||
|
||
/** | ||
* Callback called when user updates the config in UI. | ||
*/ | ||
onConfig: (config: Config) => void; | ||
} | ||
|
||
export interface ActionWizardProps { | ||
/** | ||
* List of available action factories | ||
*/ | ||
actionFactories: Array<ActionFactory<any>>; // any here to be able to pass array of ActionFactory<Config> with different configs | ||
|
||
/** | ||
* Currently selected action factory | ||
* undefined - is allowed and means that non is selected | ||
*/ | ||
currentActionFactory?: ActionFactory; | ||
/** | ||
* Action factory selected changed | ||
* null - means user click "change" and removed action factory selection | ||
*/ | ||
onActionFactoryChange: (actionFactory: ActionFactory | null) => void; | ||
|
||
/** | ||
* current config for currently selected action factory | ||
*/ | ||
config?: ActionBaseConfig; | ||
|
||
/** | ||
* config changed | ||
*/ | ||
onConfigChange: (config: ActionBaseConfig) => void; | ||
} | ||
|
||
export const ActionWizard: React.FC<ActionWizardProps> = ({ | ||
currentActionFactory, | ||
actionFactories, | ||
onActionFactoryChange, | ||
onConfigChange, | ||
config, | ||
}) => { | ||
// auto pick action factory if there is only 1 available | ||
if (!currentActionFactory && actionFactories.length === 1) { | ||
onActionFactoryChange(actionFactories[0]); | ||
} | ||
|
||
if (currentActionFactory && config) { | ||
return ( | ||
<SelectedActionFactory | ||
actionFactory={currentActionFactory} | ||
showDeselect={actionFactories.length > 1} | ||
onDeselect={() => { | ||
onActionFactoryChange(null); | ||
}} | ||
config={config} | ||
onConfigChange={newConfig => { | ||
onConfigChange(newConfig); | ||
}} | ||
/> | ||
); | ||
} | ||
|
||
return ( | ||
<ActionFactorySelector | ||
actionFactories={actionFactories} | ||
onActionFactorySelected={actionFactory => { | ||
onActionFactoryChange(actionFactory); | ||
}} | ||
/> | ||
); | ||
}; | ||
|
||
interface SelectedActionFactoryProps<Config extends ActionBaseConfig = ActionBaseConfig> { | ||
actionFactory: ActionFactory<Config>; | ||
config: Config; | ||
onConfigChange: (config: Config) => void; | ||
showDeselect: boolean; | ||
onDeselect: () => void; | ||
} | ||
|
||
export const TEST_SUBJ_SELECTED_ACTION_FACTORY = 'selected-action-factory'; | ||
|
||
const SelectedActionFactory: React.FC<SelectedActionFactoryProps> = ({ | ||
actionFactory, | ||
onDeselect, | ||
showDeselect, | ||
onConfigChange, | ||
config, | ||
}) => { | ||
return ( | ||
<div | ||
className="auaActionWizard__selectedActionFactoryContainer" | ||
data-test-subj={TEST_SUBJ_SELECTED_ACTION_FACTORY} | ||
data-testid={TEST_SUBJ_SELECTED_ACTION_FACTORY} | ||
> | ||
<header> | ||
<EuiFlexGroup alignItems="center" gutterSize="s"> | ||
{actionFactory.iconType && ( | ||
<EuiFlexItem grow={false}> | ||
<EuiIcon type={actionFactory.iconType} size="m" /> | ||
</EuiFlexItem> | ||
)} | ||
<EuiFlexItem grow={true}> | ||
<EuiText> | ||
<h4>{actionFactory.displayName}</h4> | ||
</EuiText> | ||
</EuiFlexItem> | ||
{showDeselect && ( | ||
<EuiFlexItem grow={false}> | ||
<EuiButtonEmpty size="s" onClick={() => onDeselect()}> | ||
{txtChangeButton} | ||
</EuiButtonEmpty> | ||
</EuiFlexItem> | ||
)} | ||
</EuiFlexGroup> | ||
</header> | ||
<EuiSpacer size="m" /> | ||
<div> | ||
{actionFactory.wizard({ | ||
config, | ||
onConfig: onConfigChange, | ||
})} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
interface ActionFactorySelectorProps { | ||
actionFactories: ActionFactory[]; | ||
onActionFactorySelected: (actionFactory: ActionFactory) => void; | ||
} | ||
|
||
export const TEST_SUBJ_ACTION_FACTORY_ITEM = 'action-factory-item'; | ||
|
||
const ActionFactorySelector: React.FC<ActionFactorySelectorProps> = ({ | ||
actionFactories, | ||
onActionFactorySelected, | ||
}) => { | ||
if (actionFactories.length === 0) { | ||
// this is not user facing, as it would be impossible to get into this state | ||
// just leaving for dev purposes for troubleshooting | ||
return <div>No action factories to pick from</div>; | ||
} | ||
|
||
return ( | ||
<EuiFlexGroup wrap> | ||
{actionFactories.map(actionFactory => ( | ||
<EuiKeyPadMenuItemButton | ||
className="auaActionWizard__actionFactoryItem" | ||
key={actionFactory.type} | ||
label={actionFactory.displayName} | ||
data-testid={TEST_SUBJ_ACTION_FACTORY_ITEM} | ||
data-test-subj={TEST_SUBJ_ACTION_FACTORY_ITEM} | ||
onClick={() => onActionFactorySelected(actionFactory)} | ||
> | ||
{actionFactory.iconType && <EuiIcon type={actionFactory.iconType} size="m" />} | ||
</EuiKeyPadMenuItemButton> | ||
))} | ||
</EuiFlexGroup> | ||
); | ||
}; |
14 changes: 14 additions & 0 deletions
14
x-pack/plugins/advanced_ui_actions/public/components/action_wizard/i18n.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* 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 { i18n } from '@kbn/i18n'; | ||
|
||
export const txtChangeButton = i18n.translate( | ||
'xpack.advancedUiActions.components.actionWizard.changeButton', | ||
{ | ||
defaultMessage: 'change', | ||
} | ||
); |
7 changes: 7 additions & 0 deletions
7
x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 { ActionFactory, ActionWizard } from './action_wizard'; |
Oops, something went wrong.