-
Notifications
You must be signed in to change notification settings - Fork 297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rares/webhook UI changes #2419
Rares/webhook UI changes #2419
Changes from all commits
2717ed5
e41cca3
689fff6
ef41d60
bff6758
26291c1
2d5011c
a319d5d
7d0c0ec
e354729
bb62d8b
01cd1d0
f483c26
505df5c
140868d
06ed9c1
04e375f
d99ffa2
71eff2d
44b8965
e5539a0
25f9727
8324395
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,22 @@ | ||
.hamburgerMenu { | ||
cursor: pointer; | ||
color: var(--primary-text-color); | ||
border: var(--border-weak); | ||
border-radius: var(--border-radius); | ||
background-color: var(--button-background); | ||
display: inline-flex; | ||
flex-direction: column; | ||
align-items: center; | ||
vertical-align: middle; | ||
justify-content: center; | ||
padding: 4px; | ||
|
||
&:hover { | ||
background-color: var(--button-hover-background); | ||
} | ||
|
||
&--withBackground { | ||
height: 32px; | ||
width: 30px; | ||
cursor: pointer; | ||
color: var(--primary-text-color); | ||
} | ||
|
||
&--small { | ||
height: 24px; | ||
width: 22px; | ||
cursor: pointer; | ||
color: var(--primary-text-color); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,7 @@ | |
.content { | ||
margin: 4px; | ||
} | ||
|
||
.tabs__content { | ||
padding-top: 16px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,20 @@ | ||
import React, { useCallback } from 'react'; | ||
import React, { useCallback, useState } from 'react'; | ||
|
||
import { Button, Drawer, HorizontalGroup } from '@grafana/ui'; | ||
import { Button, ConfirmModal, ConfirmModalProps, Drawer, HorizontalGroup, Tab, TabsBar } from '@grafana/ui'; | ||
import cn from 'classnames/bind'; | ||
import { observer } from 'mobx-react'; | ||
import { useHistory } from 'react-router-dom'; | ||
|
||
import GForm from 'components/GForm/GForm'; | ||
import Text from 'components/Text/Text'; | ||
import OutgoingWebhook2Status from 'containers/OutgoingWebhook2Status/OutgoingWebhook2Status'; | ||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; | ||
import { OutgoingWebhook2 } from 'models/outgoing_webhook_2/outgoing_webhook_2.types'; | ||
import { WebhookFormActionType } from 'pages/outgoing_webhooks_2/OutgoingWebhooks2.types'; | ||
import { useStore } from 'state/useStore'; | ||
import { KeyValuePair } from 'utils'; | ||
import { UserActions } from 'utils/authorization'; | ||
import { PLUGIN_ROOT } from 'utils/consts'; | ||
|
||
import { form } from './OutgoingWebhook2Form.config'; | ||
|
||
|
@@ -19,65 +24,221 @@ const cx = cn.bind(styles); | |
|
||
interface OutgoingWebhook2FormProps { | ||
id: OutgoingWebhook2['id'] | 'new'; | ||
action: 'new' | 'update'; | ||
action: WebhookFormActionType; | ||
onHide: () => void; | ||
onUpdate: () => void; | ||
onDelete: () => void; | ||
} | ||
|
||
const OutgoingWebhook2Form = observer((props: OutgoingWebhook2FormProps) => { | ||
const { id, action, onUpdate, onHide } = props; | ||
|
||
const store = useStore(); | ||
export const WebhookTabs = { | ||
Settings: new KeyValuePair('Settings', 'Settings'), | ||
LastRun: new KeyValuePair('LastRun', 'Last Run'), | ||
}; | ||
|
||
const { outgoingWebhook2Store } = store; | ||
const OutgoingWebhook2Form = observer((props: OutgoingWebhook2FormProps) => { | ||
const history = useHistory(); | ||
const { id, action, onUpdate, onHide, onDelete } = props; | ||
const [activeTab, setActiveTab] = useState<string>( | ||
action === WebhookFormActionType.EDIT_SETTINGS ? WebhookTabs.Settings.key : WebhookTabs.LastRun.key | ||
); | ||
|
||
const data = | ||
id === 'new' | ||
? { is_webhook_enabled: true, is_legacy: false } | ||
: action === 'new' | ||
? { ...outgoingWebhook2Store.items[id], is_legacy: false, name: '' } | ||
: outgoingWebhook2Store.items[id]; | ||
const { outgoingWebhook2Store } = useStore(); | ||
const isNew = action === WebhookFormActionType.NEW; | ||
const isNewOrCopy = isNew || action === WebhookFormActionType.COPY; | ||
|
||
const handleSubmit = useCallback( | ||
(data: Partial<OutgoingWebhook2>) => { | ||
(action === 'new' ? outgoingWebhook2Store.create(data) : outgoingWebhook2Store.update(id, data)).then(() => { | ||
(isNewOrCopy ? outgoingWebhook2Store.create(data) : outgoingWebhook2Store.update(id, data)).then(() => { | ||
onHide(); | ||
|
||
onUpdate(); | ||
}); | ||
}, | ||
[id] | ||
); | ||
|
||
if ( | ||
(action === WebhookFormActionType.EDIT_SETTINGS || action === WebhookFormActionType.VIEW_LAST_RUN) && | ||
!outgoingWebhook2Store.items[id] | ||
) { | ||
return null; | ||
} | ||
|
||
let data: | ||
| OutgoingWebhook2 | ||
| { | ||
is_webhook_enabled: boolean; | ||
is_legacy: boolean; | ||
}; | ||
|
||
if (isNew) { | ||
data = { is_webhook_enabled: true, is_legacy: false }; | ||
} else if (isNewOrCopy) { | ||
data = { ...outgoingWebhook2Store.items[id], is_legacy: false, name: '' }; | ||
} else { | ||
data = outgoingWebhook2Store.items[id]; | ||
} | ||
|
||
if ( | ||
(action === WebhookFormActionType.EDIT_SETTINGS || action === WebhookFormActionType.VIEW_LAST_RUN) && | ||
!outgoingWebhook2Store.items[id] | ||
) { | ||
// nothing to show if we open invalid ID for edit/last_run | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can it be unloaded in store yet at the moment this code is running? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what you mean by this. This is when you're hitting an ID that was previously deleted or it just doesn't exist. There's no such object in the store. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as I understand we don't show anything if there is no such item in the store, but in this case we need to force load it within this component |
||
return null; | ||
} | ||
|
||
if (action === WebhookFormActionType.NEW || action === WebhookFormActionType.COPY) { | ||
// show just the creation form, not the tabs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mabe just make LastRun tab disabled for those cases? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This follows the figma where on new/copy there's no tabs shown, plus the content is slightly different. |
||
return ( | ||
<Drawer scrollableContent title={'Create Outgoing Webhook'} onClose={onHide} closeOnMaskClick={false}> | ||
{renderWebhookForm()} | ||
</Drawer> | ||
); | ||
} | ||
|
||
return ( | ||
<Drawer | ||
scrollableContent | ||
title={action === 'new' ? 'Create Outgoing Webhook' : 'Edit Outgoing Webhook'} | ||
onClose={onHide} | ||
closeOnMaskClick={false} | ||
> | ||
<div className={cx('content')} data-testid="test__outgoingWebhook2EditForm"> | ||
<GForm form={form} data={data} onSubmit={handleSubmit} /> | ||
<HorizontalGroup justify={'flex-end'}> | ||
<Button variant="secondary" onClick={onHide}> | ||
Cancel | ||
</Button> | ||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}> | ||
<Button form={form.name} type="submit" disabled={data.is_legacy}> | ||
{action === 'new' ? 'Create' : 'Update'} Webhook | ||
</Button> | ||
</WithPermissionControlTooltip> | ||
</HorizontalGroup> | ||
</div> | ||
{data.is_legacy ? ( | ||
<div className={cx('content')}> | ||
<Text type="secondary">Legacy migrated webhooks are not editable.</Text> | ||
</div> | ||
) : ( | ||
'' | ||
)} | ||
// show tabbed drawer (edit/live_run) | ||
<Drawer scrollableContent title={'Outgoing webhook details'} onClose={onHide} closeOnMaskClick={false}> | ||
<TabsBar> | ||
<Tab | ||
key={WebhookTabs.Settings.key} | ||
onChangeTab={() => { | ||
setActiveTab(WebhookTabs.Settings.key); | ||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks_2/edit/${id}`); | ||
}} | ||
active={activeTab === WebhookTabs.Settings.key} | ||
label={WebhookTabs.Settings.value} | ||
/> | ||
|
||
<Tab | ||
key={WebhookTabs.LastRun.key} | ||
onChangeTab={() => { | ||
setActiveTab(WebhookTabs.LastRun.key); | ||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks_2/last_run/${id}`); | ||
}} | ||
active={activeTab === WebhookTabs.LastRun.key} | ||
label={WebhookTabs.LastRun.value} | ||
/> | ||
</TabsBar> | ||
|
||
<WebhookTabsContent | ||
id={id} | ||
action={action} | ||
activeTab={activeTab} | ||
data={data} | ||
handleSubmit={handleSubmit} | ||
onDelete={onDelete} | ||
onHide={onHide} | ||
onUpdate={onUpdate} | ||
/> | ||
</Drawer> | ||
); | ||
|
||
function renderWebhookForm() { | ||
return ( | ||
<> | ||
<div className={cx('content')} data-testid="test__outgoingWebhook2EditForm"> | ||
<GForm form={form} data={data} onSubmit={handleSubmit} /> | ||
<div className={cx('buttons')}> | ||
<HorizontalGroup justify={'flex-end'}> | ||
<Button variant="secondary" onClick={onHide}> | ||
Cancel | ||
</Button> | ||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}> | ||
<Button form={form.name} type="submit" disabled={data.is_legacy}> | ||
{isNewOrCopy ? 'Create' : 'Update'} Webhook | ||
</Button> | ||
</WithPermissionControlTooltip> | ||
</HorizontalGroup> | ||
</div> | ||
</div> | ||
</> | ||
); | ||
} | ||
}); | ||
|
||
interface WebhookTabsProps { | ||
id: OutgoingWebhook2['id'] | 'new'; | ||
activeTab: string; | ||
action: WebhookFormActionType; | ||
data: | ||
| OutgoingWebhook2 | ||
| { | ||
is_webhook_enabled: boolean; | ||
is_legacy: boolean; | ||
}; | ||
onHide: () => void; | ||
onUpdate: () => void; | ||
onDelete: () => void; | ||
handleSubmit: (data: Partial<OutgoingWebhook2>) => void; | ||
} | ||
|
||
const WebhookTabsContent: React.FC<WebhookTabsProps> = ({ | ||
id, | ||
action, | ||
activeTab, | ||
data, | ||
handleSubmit, | ||
onHide, | ||
onUpdate, | ||
onDelete, | ||
}) => { | ||
const [confirmationModal, setConfirmationModal] = useState<ConfirmModalProps>(undefined); | ||
|
||
return ( | ||
<div className={cx('tabs__content')}> | ||
{confirmationModal && ( | ||
<ConfirmModal {...(confirmationModal as ConfirmModalProps)} onDismiss={() => setConfirmationModal(undefined)} /> | ||
)} | ||
|
||
{activeTab === WebhookTabs.Settings.key && ( | ||
<> | ||
<div className={cx('content')} data-testid="test__outgoingWebhook2EditForm"> | ||
<GForm form={form} data={data} onSubmit={handleSubmit} /> | ||
<div className={cx('buttons')}> | ||
<HorizontalGroup justify={'flex-end'}> | ||
<Button variant="secondary" onClick={onHide}> | ||
Cancel | ||
</Button> | ||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}> | ||
<Button | ||
form={form.name} | ||
variant="destructive" | ||
type="button" | ||
disabled={data.is_legacy} | ||
onClick={() => { | ||
setConfirmationModal({ | ||
isOpen: true, | ||
body: 'The action cannot be undone.', | ||
confirmText: 'Delete', | ||
dismissText: 'Cancel', | ||
onConfirm: onDelete, | ||
title: `Are you sure you want to delete webhook?`, | ||
} as ConfirmModalProps); | ||
}} | ||
> | ||
Delete Webhook | ||
</Button> | ||
</WithPermissionControlTooltip> | ||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}> | ||
<Button form={form.name} type="submit" disabled={data.is_legacy}> | ||
{action === WebhookFormActionType.NEW ? 'Create' : 'Update'} Webhook | ||
</Button> | ||
</WithPermissionControlTooltip> | ||
</HorizontalGroup> | ||
</div> | ||
</div> | ||
{data.is_legacy ? ( | ||
<div className={cx('content')}> | ||
<Text type="secondary">Legacy migrated webhooks are not editable.</Text> | ||
</div> | ||
) : ( | ||
'' | ||
)} | ||
</> | ||
)} | ||
{activeTab === WebhookTabs.LastRun.key && <OutgoingWebhook2Status id={id} onUpdate={onUpdate} />} | ||
</div> | ||
); | ||
}; | ||
|
||
export default OutgoingWebhook2Form; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what the pros of using this data structure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a key-value structure for when you need to keep groups of key/value pairs. It's used in other places as well.