Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Commit

Permalink
Refactor Email Destination for Kibana (#176)
Browse files Browse the repository at this point in the history
* Add Mail destination
  * Support SSL/TLS
  * Authentification
  * Message's body is html by dedault

* Rework mail destination :
- all mail server settings are in elasticsearch.yml
- remove html support for now

* Changed references to 'Mail' to 'Email'

* Add routes and services for EmailAccount and EmailGroup APIs

* Add styles for accordion-separator and 'Manage senders' modal

* Add 'Email sender' selection and 'Manage senders' modal to 'Create destination' page for Email destinations

* Add 'Email recipients' and 'Manage email groups' modal to 'Create destination' page for Email destinations

* Load senders in ManageSenders to decouple modal from EmailSender

* Fix bug that nested options in EmailGroup emails combo box

* Load email groups in ManageEmailGroups to decouple modal from EmailRecipients

* Move 'onClickSave' processing logic from EmailSender to ManageSender

* Move 'onClickSave' processing logic from EmailRecipients to ManageEmailGroups

* Add 'Actions' dropdown for managing email senders and email groups from the Destinations page

* Fix styling on manage email sender and email group modals

* Prevent ManageSenders and ManageEmailGroups modals from closing with ESCAPE key

* Move state changing anonymous arrow functions to component functions

* Pass httpClient to getSenders() and getEmailGroups()

* Show invalid emails entered in Email Group form and fix issue with duplicate options appearing

* Update help text for Sender 'method' selection

* Update formikToDestination for Email type

* Move email_accounts and email_groups APIs under 'destinations' namespace

* Update destinationToFormik for Email type

* Update validateEmailRecipients to return all invalid email entries

* Increase width for Email modals empty prompts for better text wrapping

* Fix text misalignment for Sender form when there is validation text

* Add toast messages on success and failure for Sender and EmailGroup modals

* Hide modal footers for Sender and EmailGroup modals when there is nothing to save

* Make email verification RFC 5322 compliant

* Comma separate invalid emails in validation text

* Add initial tests for ManageSenders and ManageEmailGroups

* Update destinationToFormik tests to await on asynchronous parts

* Update snapshots

* Add updated yarn.lock

Co-authored-by: David Chauvière <[email protected]>
Co-authored-by: David Chauviere <David Chauviere [email protected]>
  • Loading branch information
3 people authored Sep 24, 2020
1 parent 9f503cb commit bbba7f2
Show file tree
Hide file tree
Showing 52 changed files with 3,595 additions and 73 deletions.
13 changes: 13 additions & 0 deletions public/less/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@
margin-bottom: 0px;
}

.accordion-separator {
background-image: linear-gradient(#D3DAE6, #D3DAE6);
background-size: 100% 1px;
background-repeat: no-repeat;
background-position: center center;
}

.modal-manage-email {
min-width: 800px;
height: 75vh;
max-height: 1000px;
}

.euiTextArea.read-only-text-area {
padding-left: 12px;
border-color: initial;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ export const ActionsMap = {
label: 'Custom webhook',
component: props => <Message isSubjectDisabled {...props} />,
},
email: {
label: 'Email notification',
component: props => <Message {...props} />,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,81 @@
* permissions and limitations under the License.
*/

import React from 'react';
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { Component } from 'react';
import {
EuiButton,
EuiContextMenuPanel,
EuiContextMenuItem,
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
} from '@elastic/eui';

import { APP_PATH } from '../../../../../utils/constants';
import { PLUGIN_NAME } from '../../../../../../utils/constants';

const DestinationsActions = () => {
return (
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButton fill href={`${PLUGIN_NAME}#${APP_PATH.CREATE_DESTINATION}`}>
Add destination
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
);
};
export default class DestinationsActions extends Component {
state = {
isActionsOpen: false,
};

getActions = () => {
return [
<EuiContextMenuItem
key="manageSenders"
onClick={() => {
this.onCloseActions();
this.props.onClickManageSenders();
}}
>
Manage email senders
</EuiContextMenuItem>,
<EuiContextMenuItem
key="manageEmailGroups"
onClick={() => {
this.onCloseActions();
this.props.onClickManageEmailGroups();
}}
>
Manage email groups
</EuiContextMenuItem>,
];
};

onCloseActions = () => {
this.setState({ isActionsOpen: false });
};

onClickActions = () => {
this.setState(prevState => ({ isActionsOpen: !prevState.isActionsOpen }));
};

export default DestinationsActions;
render() {
const { isActionsOpen } = this.state;
return (
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem>
<EuiPopover
id="destinationActionsPopover"
button={
<EuiButton onClick={this.onClickActions} iconType="arrowDown" iconSide="right">
Actions
</EuiButton>
}
isOpen={isActionsOpen}
closePopover={this.onCloseActions}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel items={this.getActions()} />
</EuiPopover>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton fill href={`${PLUGIN_NAME}#${APP_PATH.CREATE_DESTINATION}`}>
Add destination
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,36 @@ exports[`<DestinationsActions /> should render DestinationsActions 1`] = `
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<div
class="euiFlexItem"
>
<div
class="euiPopover euiPopover--anchorDownLeft"
id="destinationActionsPopover"
>
<div
class="euiPopover__anchor"
>
<button
class="euiButton euiButton--primary euiButton--iconRight"
type="button"
>
<span
class="euiButton__content"
>
<div>
EuiIconMock
</div>
<span
class="euiButton__text"
>
Actions
</span>
</span>
</button>
</div>
</div>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ exports[`<DestinationsControls /> should render DestinationsControls 1`] = `
>
Custom webhook
</option>
<option
value="email"
>
Email
</option>
</select>
<div
class="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import React from 'react';
import _ from 'lodash';
import { EuiButton } from '@elastic/eui';

import { FORMIK_INITIAL_EMAIL_GROUP_VALUES } from '../Email/utils/constants';

const AddEmailGroupButton = ({ arrayHelpers }) => (
<EuiButton onClick={() => arrayHelpers.unshift(_.cloneDeep(FORMIK_INITIAL_EMAIL_GROUP_VALUES))}>
Add email group
</EuiButton>
);

export default AddEmailGroupButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import React from 'react';
import _ from 'lodash';
import { EuiButton } from '@elastic/eui';

import { FORMIK_INITIAL_SENDER_VALUES } from '../Email/utils/constants';

const AddSenderButton = ({ arrayHelpers }) => (
<EuiButton onClick={() => arrayHelpers.unshift(_.cloneDeep(FORMIK_INITIAL_SENDER_VALUES))}>
Add sender
</EuiButton>
);

export default AddSenderButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import { EuiSpacer } from '@elastic/eui';
import EmailSender from '../../../containers/CreateDestination/EmailSender';
import EmailRecipients from '../../../containers/CreateDestination/EmailRecipients';

const propTypes = {
type: PropTypes.string.isRequired,
};
const Email = ({ httpClient, type, values }) => (
<div>
<EmailSender httpClient={httpClient} type={type} />
<EuiSpacer size="m" />
<EmailRecipients httpClient={httpClient} type={type} />
</div>
);

Email.propTypes = propTypes;

export default Email;
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import React from 'react';
import { EuiAccordion, EuiButton } from '@elastic/eui';
import { FormikComboBox, FormikFieldText } from '../../../../../components/FormControls';
import { isInvalid, hasError } from '../../../../../utils/validate';
import { validateEmailGroupEmails, validateEmailGroupName } from './utils/validate';
import { STATE } from './utils/constants';

const onEmailGroupChange = (index, emailGroup, arrayHelpers) => {
// Checking for id here since new email groups should not be marked as updated
// Also will not replace the email group state if it has already been marked as updated
if (emailGroup.id && emailGroup.state !== STATE.UPDATED) {
arrayHelpers.replace(index, {
...emailGroup,
state: STATE.UPDATED,
});
}
};

const onCreateOption = (fieldName, value, selectedOptions, setFieldValue) => {
const normalizedValue = value.trim().toLowerCase();

if (!normalizedValue) return false;

const newOption = { label: value };
setFieldValue(fieldName, [...selectedOptions, newOption]);
};

const EmailGroup = ({ emailGroup, emailOptions, arrayHelpers, context, index, onDelete }) => {
const { name } = emailGroup;
return (
<EuiAccordion
id={name}
buttonContent={!name ? 'New email group' : name}
paddingSize="l"
extraAction={
<EuiButton color="danger" size="s" onClick={onDelete}>
Remove email group
</EuiButton>
}
>
<FormikFieldText
name={`emailGroups.${index}.name`}
formRow
fieldProps={{ validate: validateEmailGroupName(context.ctx.emailGroups) }}
rowProps={{
label: 'Email group name',
helpText:
'A unique and descriptive name that is easy to search. ' +
'Valid characters are upper and lowercase a-z, 0-9, _ (underscore) and - (hyphen).',
style: { padding: '10px 0px' },
isInvalid,
error: hasError,
}}
inputProps={{
placeholder: 'my-email-group',
onChange: (e, field, form) => {
field.onChange(e);
onEmailGroupChange(index, emailGroup, arrayHelpers);
},
}}
/>
<FormikComboBox
name={`emailGroups.${index}.emails`}
formRow
fieldProps={{ validate: validateEmailGroupEmails }}
rowProps={{
label: 'Emails',
helpText: 'Search for previously used email addresses or type in new ones.',
isInvalid,
error: hasError,
}}
inputProps={{
placeholder: 'Email addresses',
options: emailOptions,
onChange: (options, field, form) => {
form.setFieldValue(`emailGroups.${index}.emails`, options);
onEmailGroupChange(index, emailGroup, arrayHelpers);
},
onBlur: (e, field, form) => {
form.setFieldTouched(`emailGroups.${index}.emails`, true);
},
onCreateOption: (value, field, form) => {
onCreateOption(`emailGroups.${index}.emails`, value, field.value, form.setFieldValue);
},
isClearable: true,
}}
/>
</EuiAccordion>
);
};

export default EmailGroup;
Loading

0 comments on commit bbba7f2

Please sign in to comment.