show delete integration btn only if allow_delete is truthy (#3377)
# What this PR does Show delete integration btn only if `allow_delete` is `true` for it ## Which issue(s) this PR fixes https://github.com/grafana/oncall-private/issues/2302 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required)
This commit is contained in:
parent
8f13e312f7
commit
53ca5331c6
4 changed files with 44 additions and 208 deletions
|
|
@ -0,0 +1,11 @@
|
|||
import React, { FC, ReactNode } from 'react';
|
||||
|
||||
interface RenderConditionallyProps {
|
||||
shouldRender?: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const RenderConditionally: FC<RenderConditionallyProps> = ({ shouldRender, children }) =>
|
||||
shouldRender ? <>{children}</> : null;
|
||||
|
||||
export default RenderConditionally;
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
.modal {
|
||||
width: 840px;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
max-height: 550px;
|
||||
overflow: auto;
|
||||
scroll-snap-type: y mandatory;
|
||||
padding: 0 10px 10px 0;
|
||||
min-width: 840px;
|
||||
}
|
||||
|
||||
.cards_centered {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 240px;
|
||||
height: 88px;
|
||||
scroll-snap-align: start;
|
||||
scroll-snap-stop: normal;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.card_featured {
|
||||
width: 768px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
top: 28px;
|
||||
right: 28px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 10px 0 10px 0;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-integration {
|
||||
width: 400px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
import React, { ChangeEvent, useCallback, useState } from 'react';
|
||||
|
||||
import { EmptySearchResult, HorizontalGroup, Input, Modal, VerticalGroup, Tag, Field } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import Block from 'components/GBlock/Block';
|
||||
import IntegrationLogo from 'components/IntegrationLogo/IntegrationLogo';
|
||||
import Text from 'components/Text/Text';
|
||||
import GrafanaTeamSelect from 'containers/GrafanaTeamSelect/GrafanaTeamSelect';
|
||||
import { AlertReceiveChannelOption } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { GrafanaTeam } from 'models/grafana_team/grafana_team.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import styles from './CreateAlertReceiveChannelContainer.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface CreateAlertReceiveChannelContainerProps {
|
||||
onHide: () => void;
|
||||
onCreate: (option: AlertReceiveChannelOption, team: GrafanaTeam['id']) => void;
|
||||
}
|
||||
|
||||
const CreateAlertReceiveChannelContainer = observer((props: CreateAlertReceiveChannelContainerProps) => {
|
||||
const { onHide, onCreate } = props;
|
||||
|
||||
const { alertReceiveChannelStore, userStore } = useStore();
|
||||
const user = userStore.currentUser;
|
||||
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
|
||||
const [selectedTeam, setSelectedTeam] = useState<GrafanaTeam['id']>(user.current_team);
|
||||
|
||||
const handleNewIntegrationOptionSelectCallback = useCallback(
|
||||
(option: AlertReceiveChannelOption) => {
|
||||
onHide();
|
||||
onCreate(option, selectedTeam);
|
||||
},
|
||||
[onCreate, onHide, selectedTeam]
|
||||
);
|
||||
|
||||
const handleChangeFilter = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterValue(e.currentTarget.value);
|
||||
}, []);
|
||||
|
||||
const { alertReceiveChannelOptions } = alertReceiveChannelStore;
|
||||
|
||||
const options = alertReceiveChannelOptions
|
||||
? alertReceiveChannelOptions.filter((option: AlertReceiveChannelOption) =>
|
||||
option.display_name.toLowerCase().includes(filterValue.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<HorizontalGroup className={cx('title')}>
|
||||
<Text.Title level={4}> Create Integration</Text.Title>
|
||||
</HorizontalGroup>
|
||||
}
|
||||
isOpen
|
||||
closeOnEscape={false}
|
||||
onDismiss={onHide}
|
||||
className={cx('modal')}
|
||||
>
|
||||
<div className={cx('select-team')}>
|
||||
<Field
|
||||
label="Assign to team"
|
||||
description="OnCall teams allow you to organize integrations so you can filter and set up access. "
|
||||
>
|
||||
<GrafanaTeamSelect withoutModal onSelect={setSelectedTeam} defaultValue={user.current_team} />
|
||||
</Field>
|
||||
</div>
|
||||
<hr />
|
||||
<div className={cx('search-integration')}>
|
||||
<Input autoFocus value={filterValue} placeholder="Search integrations ..." onChange={handleChangeFilter} />
|
||||
</div>
|
||||
<div className={cx('cards', { cards_centered: !options.length })} data-testid="create-integration-modal">
|
||||
{options.length ? (
|
||||
options.map((alertReceiveChannelChoice) => {
|
||||
return (
|
||||
<Block
|
||||
bordered
|
||||
shadowed
|
||||
onClick={() => {
|
||||
handleNewIntegrationOptionSelectCallback(alertReceiveChannelChoice);
|
||||
}}
|
||||
key={alertReceiveChannelChoice.value}
|
||||
className={cx('card', { card_featured: alertReceiveChannelChoice.featured })}
|
||||
>
|
||||
<div className={cx('card-bg')}>
|
||||
<IntegrationLogo integration={alertReceiveChannelChoice} scale={0.2} />
|
||||
</div>
|
||||
<div className={cx('title')}>
|
||||
<VerticalGroup spacing="none">
|
||||
<Text strong data-testid="integration-display-name">
|
||||
{alertReceiveChannelChoice.display_name}
|
||||
</Text>
|
||||
<Text type="secondary" size="small">
|
||||
{alertReceiveChannelChoice.short_description}
|
||||
</Text>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
{alertReceiveChannelChoice.featured && (
|
||||
<Tag name="Quick connect" className={cx('tag')} colorIndex={7} />
|
||||
)}
|
||||
</Block>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<EmptySearchResult>Could not find anything matching your query</EmptySearchResult>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default CreateAlertReceiveChannelContainer;
|
||||
|
|
@ -29,6 +29,7 @@ import {
|
|||
initErrorDataState,
|
||||
} from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers';
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import RenderConditionally from 'components/RenderConditionally/RenderConditionally';
|
||||
import Text from 'components/Text/Text';
|
||||
import TextEllipsisTooltip from 'components/TextEllipsisTooltip/TextEllipsisTooltip';
|
||||
import TooltipBadge from 'components/TooltipBadge/TooltipBadge';
|
||||
|
|
@ -540,39 +541,39 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
</HorizontalGroup>
|
||||
</div>
|
||||
</CopyToClipboard>
|
||||
|
||||
<div className={cx('thin-line-break')} />
|
||||
|
||||
<WithPermissionControlTooltip key="delete" userAction={UserActions.IntegrationsWrite}>
|
||||
<div className={cx('integrations-actionItem')}>
|
||||
<div
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
confirmationModal: {
|
||||
isOpen: true,
|
||||
confirmText: 'Delete',
|
||||
dismissText: 'Cancel',
|
||||
onConfirm: () => this.handleDeleteAlertReceiveChannel(item.id),
|
||||
title: 'Delete integration',
|
||||
body: (
|
||||
<Text type="primary">
|
||||
Are you sure you want to delete <Emoji text={item.verbal_name} /> integration?
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
});
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Text type="danger">
|
||||
<HorizontalGroup spacing={'xs'}>
|
||||
<Icon name="trash-alt" />
|
||||
<span>Delete Integration</span>
|
||||
</HorizontalGroup>
|
||||
</Text>
|
||||
<RenderConditionally shouldRender={item.allow_delete}>
|
||||
<div className={cx('thin-line-break')} />
|
||||
<WithPermissionControlTooltip key="delete" userAction={UserActions.IntegrationsWrite}>
|
||||
<div className={cx('integrations-actionItem')}>
|
||||
<div
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
confirmationModal: {
|
||||
isOpen: true,
|
||||
confirmText: 'Delete',
|
||||
dismissText: 'Cancel',
|
||||
onConfirm: () => this.handleDeleteAlertReceiveChannel(item.id),
|
||||
title: 'Delete integration',
|
||||
body: (
|
||||
<Text type="primary">
|
||||
Are you sure you want to delete <Emoji text={item.verbal_name} /> integration?
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="u-width-100"
|
||||
>
|
||||
<Text type="danger">
|
||||
<HorizontalGroup spacing={'xs'}>
|
||||
<Icon name="trash-alt" />
|
||||
<span>Delete Integration</span>
|
||||
</HorizontalGroup>
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</WithPermissionControlTooltip>
|
||||
</WithPermissionControlTooltip>
|
||||
</RenderConditionally>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue