Skip to content

Commit

Permalink
Add slack channel name input field in domain details (#2838)
Browse files Browse the repository at this point in the history
Signed-off-by: craman <[email protected]>
Co-authored-by: craman <[email protected]>
  • Loading branch information
chandrasekhar1996 and craman authored Jan 27, 2025
1 parent d7d98c4 commit 3335c49
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 16 deletions.
33 changes: 33 additions & 0 deletions ui/src/__tests__/spec/tests/domain.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,39 @@ describe('Domain', () => {
);
});

it('should successfully add and clear domain slack channel', async () => {
await browser.newUser();
await browser.url(`/`);
await expect(browser).toHaveUrl(expect.stringContaining('athenz'));

let testDomain = await $('a*=athenz.dev.functional-test');
await browser.waitUntil(async () => await testDomain.isClickable());
await testDomain.click();

// expand domain details
let expand = await $(
`.//*[local-name()="svg" and @data-wdio="domain-details-expand-icon"]`
);
await expand.click();

// click add business service
let addSlackChannel = await $('a[data-testid="add-slack-channel"]');
await browser.waitUntil(
async () => await addSlackChannel.isClickable()
);
await addSlackChannel.click();

let randomInt = Math.floor(Math.random() * 100); // random number to append to slack channel name
let slackChannelName = 'slack-channel-' + randomInt;
let slackChannelInput = await $('input[name="slack-channel-input"]');
await slackChannelInput.addValue(slackChannelName);
let submitButton = await $('button*=Submit');
await submitButton.click();
await expect(addSlackChannel).toHaveText(
expect.stringContaining(slackChannelName)
);
});

it(TEST_ADD_BUSINESS_SERVICE_INPUT_PRESERVES_CONTENTS_ON_BLUR, async () => {
currentTest =
TEST_ADD_BUSINESS_SERVICE_INPUT_PRESERVES_CONTENTS_ON_BLUR;
Expand Down
72 changes: 72 additions & 0 deletions ui/src/components/header/DomainDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import Icon from '../denali/icons/Icon';
import AddPoc from '../member/AddPoc';
import { selectAllUsers } from '../../redux/selectors/user';
import AddEnvironmentModal from '../modal/AddEnvironmentModal';
import AddSlackChannelModal from '../modal/AddSlackChannelModal';

const DomainSectionDiv = styled.div`
margin: 20px 0;
Expand Down Expand Up @@ -127,6 +128,7 @@ class DomainDetails extends React.Component {
),
expandedDomain: false,
environmentName: this.props.domainDetails.environment || 'add',
slackChannel: this.props.domainDetails.slackChannel || 'add',
};
this.showError = this.showError.bind(this);
this.closeModal = this.closeModal.bind(this);
Expand Down Expand Up @@ -197,6 +199,41 @@ class DomainDetails extends React.Component {
}
}

onClickSlackChannel() {
this.setState({
showSlackChannelModal: true,
});
}

onSlackChannelUpdateSuccessCb(slackChannelName) {
let newState = {
showSlackChannelModal: false,
showSuccess: true,
};

if (!!!slackChannelName) {
slackChannelName = 'add';
}
newState.slackChannel = slackChannelName;
newState.successMessage = 'Successfully updated Slack channel';
this.setState(newState);
setTimeout(
() =>
this.setState({
showSuccess: false,
}),
MODAL_TIME_OUT + 1000
);
}

onClickSlackChannelCancel() {
this.setState({
showSlackChannelModal: false,
errorMessage: null,
errorMessageForModal: '',
});
}

onClickPointOfContactCancel() {
this.setState({
showPoc: false,
Expand Down Expand Up @@ -505,6 +542,7 @@ class DomainDetails extends React.Component {
this.state.poc,
'security-owner'
);
let onClickSlackChannel = this.onClickSlackChannel.bind(this);
let contactType;
let pocName;
let openPocModal;
Expand Down Expand Up @@ -553,6 +591,24 @@ class DomainDetails extends React.Component {
''
);

let slackChannelModal = this.state.showSlackChannelModal ? (
<AddSlackChannelModal
domain={this.props.domainDetails.name}
title='Slack Channel'
isOpen={this.state.showSlackChannelModal}
onCancel={this.onClickSlackChannelCancel.bind(this)}
errorMessage={this.state.errorMessageForModal}
onSlackChannelUpdateSuccessCb={this.onSlackChannelUpdateSuccessCb.bind(
this
)}
slackChannelName={this.state.slackChannel}
csrf={this.props._csrf}
api={this.api}
/>
) : (
''
);

return (
<DomainSectionDiv data-testid='domain-details'>
<DetailsDiv>
Expand Down Expand Up @@ -675,6 +731,7 @@ class DomainDetails extends React.Component {
/>
) : null}
{environmentModal}
{slackChannelModal}
</DetailsDiv>
{this.state.expandedDomain ? (
<DetailsDiv>
Expand Down Expand Up @@ -735,6 +792,21 @@ class DomainDetails extends React.Component {
</DivStyledBusinessService>
<LabelDiv>ENVIRONMENT</LabelDiv>
</SectionDiv>
<SectionDiv>
<DivStyledBusinessService
title={this.state.slackChannel}
>
<StyledAnchor
data-testid='add-slack-channel'
onClick={this.onClickSlackChannel.bind(
this
)}
>
{this.state.slackChannel}
</StyledAnchor>
</DivStyledBusinessService>
<LabelDiv>SLACK CHANNEL</LabelDiv>
</SectionDiv>
</DetailsDiv>
) : null}
</DomainSectionDiv>
Expand Down
177 changes: 177 additions & 0 deletions ui/src/components/modal/AddSlackChannelModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright The Athenz Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 { connect } from 'react-redux';
import AddModal from '../modal/AddModal';
import styled from '@emotion/styled';
import InputLabel from '../denali/InputLabel';
import { colors } from '../denali/styles';
import { selectAllUsers } from '../../redux/selectors/user';
import RequestUtils from '../utils/RequestUtils';
import Input from '../denali/Input';
import RegexUtils from '../utils/RegexUtils';

const SectionsDiv = styled.div`
width: 760px;
text-align: left;
background-color: ${colors.white};
`;

const SectionDiv = styled.div`
align-items: flex-start;
display: flex;
flex-flow: row nowrap;
padding: 10px 30px;
`;

const ContentDiv = styled.div`
flex: 1 1;
display: flex;
flex-flow: row nowrap;
`;

const StyledInputLabel = styled(InputLabel)`
flex-basis: 32%;
font-weight: 600;
line-height: 36px;
`;

const StyledInput = styled(Input)`
flex-basis: 80%;
`;

class AddSlackChannelModal extends React.Component {
constructor(props) {
super(props);
this.api = props.api;
this.onSubmit = this.onSubmit.bind(this);
let slackChannelName =
this.props.slackChannelName === 'add'
? ''
: this.props.slackChannelName;
this.state = {
showModal: this.props.isOpen,
slackChannelName: slackChannelName,
};
}

onSubmit() {
if (!!this.state.slackChannelName) {
if (
!RegexUtils.validate(
this.state.slackChannelName,
/^[a-z0-9-_]{1}[a-z0-9-_]{0,79}$/
)
) {
this.setState({
errorMessage:
'Slack channel name is invalid. It should be between 1-80 characters long and can only contain lowercase letters, numbers, hyphens and underscores.',
});
return;
}
}

let meta = {
slackChannel: this.state.slackChannelName,
};
let domainName = this.props.domain;
let auditMsg = `Updating Slack channel ${this.state.slackChannelName} for domain ${this.props.domain}`;
let csrf = this.props.csrf;
this.api
.putMeta(domainName, domainName, meta, auditMsg, csrf, 'domain')
.then(() => {
this.setState({
showModal: false,
});
this.props.onSlackChannelUpdateSuccessCb(
this.state.slackChannelName
);
})
.catch((err) => {
this.setState({
errorMessage: RequestUtils.xhrErrorCheckHelper(err),
});
});
}

inputChanged(key, evt) {
let value = '';
if (evt.target) {
value = evt.target.value;
} else {
value = evt ? evt : '';
}
this.setState({ [key]: value });
}

render() {
let memberLabel = 'Slack Channel Name';
let title =
'Update slack channel to receive notifications for ' +
this.props.domain;
let sections = (
<SectionsDiv
autoComplete={'off'}
data-testid='add-slack-channel-form'
>
<SectionDiv>
<StyledInputLabel htmlFor='member-name'>
{memberLabel}
</StyledInputLabel>
<ContentDiv>
<StyledInput
id='slack-channel-input'
name='slack-channel-input'
value={this.state.slackChannelName}
onChange={this.inputChanged.bind(
this,
'slackChannelName'
)}
autoComplete={'off'}
placeholder={'sample-channel'}
/>
</ContentDiv>
</SectionDiv>
</SectionsDiv>
);

return (
<div data-testid='add-slack-channel'>
<AddModal
isOpen={this.state.showModal}
cancel={this.props.onCancel}
submit={this.onSubmit}
title={title}
errorMessage={this.state.errorMessage}
sections={sections}
overflowY={'none'}
/>
</div>
);
}
}

const mapStateToProps = (state, props) => {
return {
...props,
userList: selectAllUsers(state),
};
};
const mapDispatchToProps = (dispatch) => ({});
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddSlackChannelModal);
2 changes: 1 addition & 1 deletion ui/src/config/msd.json
Original file line number Diff line number Diff line change
Expand Up @@ -3531,4 +3531,4 @@
"expected": "OK"
}
]
}
}
2 changes: 1 addition & 1 deletion ui/src/config/ums.json
Original file line number Diff line number Diff line change
Expand Up @@ -2783,4 +2783,4 @@
}
}
]
}
}
2 changes: 1 addition & 1 deletion ui/src/config/zms.json
Original file line number Diff line number Diff line change
Expand Up @@ -12348,4 +12348,4 @@
"expected": "OK"
}
]
}
}
2 changes: 1 addition & 1 deletion ui/src/config/zts.json
Original file line number Diff line number Diff line change
Expand Up @@ -4551,4 +4551,4 @@
"expected": "OK"
}
]
}
}
21 changes: 21 additions & 0 deletions ui/src/server/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright The Athenz Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/

const validationPatterns = {
domainSlackChannel: /^[a-z0-9-_]{1}[a-z0-9-_]{0,79}$/,
};

module.exports = validationPatterns;
Loading

0 comments on commit 3335c49

Please sign in to comment.