Compare commits

...

4 Commits

Author SHA1 Message Date
Corey Alexander c4f92fc4d7 Change to warning icon since thats the one I wanted anyways 2022-07-22 00:10:42 -04:00
Corey Alexander d764c8a302 Start displaying status with an icon and add createdAt date too 2022-07-21 21:51:13 -04:00
Corey Alexander 611b584ca4 Get a basic route setup that lists all deliveries 2022-07-21 21:19:34 -04:00
Corey Alexander c221b96a74 Move the current options into a menu so that its easier to add the option to view deliveries 2022-07-21 20:14:32 -04:00
11 changed files with 275 additions and 52 deletions
+111
View File
@@ -0,0 +1,111 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useMenuState } from "reakit/Menu";
import WebhookSubscription from "~/models/WebhookSubscription";
import WebhookDeliveries from "~/scenes/Settings/components/WebhookDeliveries";
import WebhookSubscriptionRevokeDialog from "~/scenes/Settings/components/WebhookSubscriptionDeleteDialog";
import WebhookSubscriptionEdit from "~/scenes/Settings/components/WebhookSubscriptionEdit";
import ContextMenu from "~/components/ContextMenu";
import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton";
import Template from "~/components/ContextMenu/Template";
import Modal from "~/components/Modal";
import useBoolean from "~/hooks/useBoolean";
import useStores from "~/hooks/useStores";
type Props = {
webhook: WebhookSubscription;
};
function WebhookMenu({ webhook }: Props) {
const { t } = useTranslation();
const menu = useMenuState();
const { dialogs } = useStores();
const showDeletionConfirmation = React.useCallback(() => {
dialogs.openModal({
title: t("Delete webhook"),
isCentered: true,
content: (
<WebhookSubscriptionRevokeDialog
onSubmit={dialogs.closeAllModals}
webhook={webhook}
/>
),
});
}, [t, dialogs, webhook]);
const [
editModalOpen,
handleEditModalOpen,
handleEditModalClose,
] = useBoolean();
const renderEditModal = React.useCallback(
() => (
<Modal
title={t("Edit webhook")}
onRequestClose={handleEditModalClose}
isOpen={editModalOpen}
>
<WebhookSubscriptionEdit
onSubmit={handleEditModalClose}
webhookSubscription={webhook}
/>
</Modal>
),
[t, handleEditModalClose, editModalOpen, webhook]
);
const [
viewModalOpen,
handleViewModalOpen,
handleViewModalClose,
] = useBoolean();
const renderViewModal = React.useCallback(
() => (
<Modal
title={t("Webhook Deliveries")}
onRequestClose={handleViewModalClose}
isOpen={viewModalOpen}
>
<WebhookDeliveries webhook={webhook} />
</Modal>
),
[t, handleViewModalClose, viewModalOpen]
);
return (
<>
<OverflowMenuButton aria-label={t("Show menu")} {...menu} />
<ContextMenu {...menu} aria-label={t("Member options")}>
<Template
{...menu}
items={[
{
type: "button",
title: t("View Deliveries"),
onClick: handleViewModalOpen,
},
{
type: "button",
title: t("Edit"),
dangerous: false,
onClick: handleEditModalOpen,
},
{
type: "button",
title: t("Delete"),
dangerous: true,
onClick: showDeletionConfirmation,
},
]}
/>
</ContextMenu>
{renderEditModal()}
{renderViewModal()}
</>
);
}
export default WebhookMenu;
+15
View File
@@ -0,0 +1,15 @@
import { observable } from "mobx";
import BaseModel from "./BaseModel";
import Field from "./decorators/Field";
class WebhookDelivery extends BaseModel {
@Field
@observable
id: string;
@Field
@observable
status: string;
}
export default WebhookDelivery;
@@ -0,0 +1,75 @@
import { SmileyIcon, WarningIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import WebhookDelivery from "~/models/WebhookDelivery";
import WebhookSubscription from "~/models/WebhookSubscription";
import Flex from "~/components/Flex";
import TableFromParams from "~/components/TableFromParams";
import Time from "~/components/Time";
import useStores from "~/hooks/useStores";
type Props = {
webhook: WebhookSubscription;
};
type CellProps<T> = {
value: T;
row: { original: WebhookDelivery };
};
const WebhookDeliveries = ({ webhook }: Props) => {
const { t } = useTranslation();
const { webhookDeliveries } = useStores();
const [deliveries, setDeliveries] = React.useState<WebhookDelivery[]>([]);
React.useEffect(() => {
async function fetch() {
const resp = await webhookDeliveries.fetchPage({
webhookSubscriptionId: webhook.id,
});
setDeliveries(resp);
}
fetch();
}, [webhookDeliveries, webhook]);
return (
<div>
<TableFromParams
columns={[
{
id: "status",
Header: t("Status"),
accessor: "status",
Cell: ({ value }: CellProps<string>) => (
<Flex align="center" gap={5}>
{value === "failed" ? <WarningIcon /> : <SmileyIcon />}
<span>{value}</span>
</Flex>
),
},
{
id: "id",
Header: t("ID"),
accessor: "id",
Cell: ({ value }: CellProps<string>) => value,
},
{
id: "createdAt",
Header: "",
accessor: "createdAt",
Cell: ({ value }: CellProps<string>) => <Time dateTime={value} />,
},
]}
data={deliveries}
isLoading={false}
page={0}
defaultSortDirection="DESC"
/>
</div>
);
};
export default WebhookDeliveries;
@@ -1,16 +1,10 @@
import { EditIcon, TrashIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import WebhookSubscription from "~/models/WebhookSubscription";
import Badge from "~/components/Badge";
import Button from "~/components/Button";
import ListItem from "~/components/List/Item";
import Modal from "~/components/Modal";
import useBoolean from "~/hooks/useBoolean";
import useStores from "~/hooks/useStores";
import WebhookSubscriptionRevokeDialog from "./WebhookSubscriptionDeleteDialog";
import WebhookSubscriptionEdit from "./WebhookSubscriptionEdit";
import WebhookMenu from "~/menus/WebhookMenu";
type Props = {
webhook: WebhookSubscription;
@@ -18,25 +12,6 @@ type Props = {
const WebhookSubscriptionListItem = ({ webhook }: Props) => {
const { t } = useTranslation();
const { dialogs } = useStores();
const [
editModalOpen,
handleEditModalOpen,
handleEditModalClose,
] = useBoolean();
const showDeletionConfirmation = React.useCallback(() => {
dialogs.openModal({
title: t("Delete webhook"),
isCentered: true,
content: (
<WebhookSubscriptionRevokeDialog
onSubmit={dialogs.closeAllModals}
webhook={webhook}
/>
),
});
}, [t, dialogs, webhook]);
return (
<ListItem
@@ -54,30 +29,7 @@ const WebhookSubscriptionListItem = ({ webhook }: Props) => {
{t("Subscribed events")}: <code>{webhook.events.join(", ")}</code>
</>
}
actions={
<>
<Button
onClick={showDeletionConfirmation}
icon={<TrashIcon />}
neutral
>
{t("Delete")}
</Button>
<Button icon={<EditIcon />} onClick={handleEditModalOpen} neutral>
{t("Edit")}
</Button>
<Modal
title={t("Edit webhook")}
onRequestClose={handleEditModalClose}
isOpen={editModalOpen}
>
<WebhookSubscriptionEdit
onSubmit={handleEditModalClose}
webhookSubscription={webhook}
/>
</Modal>
</>
}
actions={<WebhookMenu webhook={webhook} />}
/>
);
};
+4
View File
@@ -22,6 +22,7 @@ import ToastsStore from "./ToastsStore";
import UiStore from "./UiStore";
import UsersStore from "./UsersStore";
import ViewsStore from "./ViewsStore";
import WebhookDeliveriesStore from "./WebhookDeliveriesStore";
import WebhookSubscriptionsStore from "./WebhookSubscriptionStore";
export default class RootStore {
@@ -50,6 +51,7 @@ export default class RootStore {
toasts: ToastsStore;
fileOperations: FileOperationsStore;
webhookSubscriptions: WebhookSubscriptionsStore;
webhookDeliveries: WebhookDeliveriesStore;
constructor() {
// PoliciesStore must be initialized before AuthStore
@@ -78,6 +80,7 @@ export default class RootStore {
this.fileOperations = new FileOperationsStore(this);
this.toasts = new ToastsStore();
this.webhookSubscriptions = new WebhookSubscriptionsStore(this);
this.webhookDeliveries = new WebhookDeliveriesStore(this);
}
logout() {
@@ -104,5 +107,6 @@ export default class RootStore {
this.users.clear();
this.views.clear();
this.webhookSubscriptions.clear();
this.webhookDeliveries.clear();
}
}
+13
View File
@@ -0,0 +1,13 @@
import WebhookDelivery from "~/models/WebhookDelivery";
import BaseStore, { RPCAction } from "./BaseStore";
import RootStore from "./RootStore";
export default class WebhookDerliveriesStore extends BaseStore<
WebhookDelivery
> {
actions = [RPCAction.List];
constructor(rootStore: RootStore) {
super(rootStore, WebhookDelivery);
}
}
+2
View File
@@ -21,6 +21,7 @@ import presentTeam from "./team";
import presentUser from "./user";
import presentView from "./view";
import presentWebhook from "./webhook";
import presentWebhookDelivery from "./webhookDelivery";
import presentWebhookSubscription from "./webhookSubscription";
export {
@@ -48,4 +49,5 @@ export {
presentCollectionGroupMembership,
presentWebhook,
presentWebhookSubscription,
presentWebhookDelivery,
};
+10
View File
@@ -0,0 +1,10 @@
import { WebhookDelivery } from "@server/models";
export default function present(webhook: WebhookDelivery) {
return {
id: webhook.id,
status: webhook.status,
createdAt: webhook.createdAt,
updatedAt: webhook.updatedAt,
};
}
+2
View File
@@ -29,6 +29,7 @@ import stars from "./stars";
import team from "./team";
import users from "./users";
import views from "./views";
import webhookDeliveries from "./webhookDeliveries";
import webhookSubscriptions from "./webhookSubscriptions";
const api = new Koa();
@@ -71,6 +72,7 @@ router.use("/", utils.routes());
router.use("/", groups.routes());
router.use("/", fileOperationsRoute.routes());
router.use("/", webhookSubscriptions.routes());
router.use("/", webhookDeliveries.routes());
if (env.ENVIRONMENT === "development") {
router.use("/", developer.routes());
+35
View File
@@ -0,0 +1,35 @@
import Router from "koa-router";
import auth from "@server/middlewares/authentication";
import { WebhookDelivery } from "@server/models";
import { authorize } from "@server/policies";
import { presentWebhookDelivery } from "@server/presenters";
import pagination from "./middlewares/pagination";
const router = new Router();
router.post(
"webhookDeliverys.list",
auth({ admin: true }),
pagination(),
async (ctx) => {
const { user } = ctx.state;
const { webhookSubscriptionId } = ctx.request.body;
authorize(user, "listWebhookDeliveries", user.team);
const deliveries = await WebhookDelivery.findAll({
where: {
webhookSubscriptionId,
},
order: [["createdAt", "DESC"]],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
});
ctx.body = {
pagination: ctx.state.pagination,
data: deliveries.map(presentWebhookDelivery),
};
}
);
export default router;
+6 -2
View File
@@ -321,6 +321,10 @@
"Revoke invite": "Revoke invite",
"Activate account": "Activate account",
"Suspend account": "Suspend account",
"Delete webhook": "Delete webhook",
"Edit webhook": "Edit webhook",
"Webhook Deliveries": "Webhook Deliveries",
"View Deliveries": "View Deliveries",
"API token created": "API token created",
"Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".": "Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".",
"The document archive is empty at the moment.": "The document archive is empty at the moment.",
@@ -592,6 +596,8 @@
"Active": "Active",
"Everyone": "Everyone",
"Admins": "Admins",
"Status": "Status",
"ID": "ID",
"Are you sure you want to delete the {{ name }} webhook?": "Are you sure you want to delete the {{ name }} webhook?",
"Webhook updated": "Webhook updated",
"Update": "Update",
@@ -601,10 +607,8 @@
"URL": "URL",
"All events": "All events",
"All {{ groupName }} events": "All {{ groupName }} events",
"Delete webhook": "Delete webhook",
"Disabled": "Disabled",
"Subscribed events": "Subscribed events",
"Edit webhook": "Edit webhook",
"Webhook created": "Webhook created",
"Logo updated": "Logo updated",
"Unable to upload new logo": "Unable to upload new logo",