UX: Apply admin table to webhooks (#30317)

* UX: Apply admin table classes for consistent mobile styling on the web hooks page

* DEV: Remove icon on the status component; update status classes

* DEV: Update tests for webhook status component

* DEV: add space var with a smaller value

* DEV: Add styling for different status labels
This commit is contained in:
Ella E. 2024-12-17 08:52:29 -07:00 committed by GitHub
parent e04f535601
commit 37f032752e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 116 additions and 96 deletions

View File

@ -1,10 +1,8 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import icon from "discourse-common/helpers/d-icon";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
export default class WebhookStatus extends Component { export default class WebhookStatus extends Component {
iconNames = ["far-circle", "circle-xmark", "circle", "circle"]; statusClasses = ["--inactive", "--critical", "--success", "--inactive"];
iconClasses = ["text-muted", "text-danger", "text-successful", "text-muted"];
get status() { get status() {
const lastStatus = this.args.webhook.get("last_delivery_status"); const lastStatus = this.args.webhook.get("last_delivery_status");
@ -15,16 +13,17 @@ export default class WebhookStatus extends Component {
return i18n(`admin.web_hooks.delivery_status.${this.status.name}`); return i18n(`admin.web_hooks.delivery_status.${this.status.name}`);
} }
get iconName() { get statusClass() {
return this.iconNames[this.status.id - 1]; return this.statusClasses[this.status.id - 1];
}
get iconClass() {
return this.iconClasses[this.status.id - 1];
} }
<template> <template>
{{icon this.iconName class=this.iconClass}} <div role="status" class="status-label {{this.statusClass}}">
<div class="status-label-indicator">
</div>
<div class="status-label-text">
{{this.deliveryStatus}} {{this.deliveryStatus}}
</div>
</div>
</template> </template>
} }

View File

@ -14,19 +14,34 @@
{{#if this.model}} {{#if this.model}}
<LoadMore @selector=".web-hooks tr" @action={{this.loadMore}}> <LoadMore @selector=".web-hooks tr" @action={{this.loadMore}}>
<table class="web-hooks grid"> <table class="d-admin-table web-hooks">
<thead> <thead>
<tr> <tr>
<th>{{i18n "admin.web_hooks.delivery_status.title"}}</th>
<th>{{i18n "admin.web_hooks.payload_url"}}</th> <th>{{i18n "admin.web_hooks.payload_url"}}</th>
<th>{{i18n "admin.web_hooks.description_label"}}</th> <th>{{i18n "admin.web_hooks.description_label"}}</th>
<th>{{i18n "admin.web_hooks.controls"}}</th> <th>{{i18n "admin.web_hooks.delivery_status.title"}}</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{#each this.model as |webhook|}} {{#each this.model as |webhook|}}
<tr> <tr class="d-admin-row__content">
<td class="delivery-status"> <td class="d-admin-row__overview payload-url">
<LinkTo @route="adminWebHooks.edit" @model={{webhook}}>
{{webhook.payload_url}}
</LinkTo>
</td>
<td class="d-admin-row__detail description">
<div class="d-admin-row__mobile-label">
{{i18n "admin.web_hooks.description_label"}}
</div>
{{webhook.description}}
</td>
<td class="d-admin-row__detail delivery-status">
<div class="d-admin-row__mobile-label">
{{i18n "admin.web_hooks.delivery_status.title"}}
</div>
<LinkTo @route="adminWebHooks.show" @model={{webhook}}> <LinkTo @route="adminWebHooks.show" @model={{webhook}}>
<WebhookStatus <WebhookStatus
@deliveryStatuses={{this.deliveryStatuses}} @deliveryStatuses={{this.deliveryStatuses}}
@ -34,13 +49,8 @@
/> />
</LinkTo> </LinkTo>
</td> </td>
<td class="payload-url"> <td class="d-admin-row__controls controls">
<LinkTo @route="adminWebHooks.edit" @model={{webhook}}> <div class="d-admin-row__controls-options">
{{webhook.payload_url}}
</LinkTo>
</td>
<td class="description">{{webhook.description}}</td>
<td class="controls">
<LinkTo <LinkTo
@route="adminWebHooks.edit" @route="adminWebHooks.edit"
@model={{webhook}} @model={{webhook}}
@ -56,6 +66,7 @@
@title="delete" @title="delete"
class="destroy btn-danger" class="destroy btn-danger"
/> />
</div>
</td> </td>
</tr> </tr>
{{/each}} {{/each}}

View File

@ -33,7 +33,7 @@ module("Integration | Component | webhook-status", function (hooks) {
assert.dom().hasText("Failed"); assert.dom().hasText("Failed");
}); });
test("iconName", async function (assert) { test("statusLabelClass", async function (assert) {
const webhook = new CoreFabricators(getOwner(this)).webhook(); const webhook = new CoreFabricators(getOwner(this)).webhook();
await render(<template> await render(<template>
<WebhookStatus <WebhookStatus
@ -42,30 +42,18 @@ module("Integration | Component | webhook-status", function (hooks) {
/> />
</template>); </template>);
assert.dom(".d-icon-far-circle").exists(); assert.dom(".status-label").hasClass("--inactive");
webhook.set("last_delivery_status", 2); webhook.set("last_delivery_status", 2);
await rerender(); await rerender();
assert.dom(".status-label").hasClass("--critical");
assert.dom(".d-icon-circle-xmark").exists(); webhook.set("last_delivery_status", 3);
});
test("iconClass", async function (assert) {
const webhook = new CoreFabricators(getOwner(this)).webhook();
await render(<template>
<WebhookStatus
@deliveryStatuses={{DELIVERY_STATUSES}}
@webhook={{webhook}}
/>
</template>);
assert.dom(".d-icon").hasClass("text-muted");
webhook.set("last_delivery_status", 2);
await rerender(); await rerender();
assert.dom(".status-label").hasClass("--success");
assert.dom(".d-icon").hasClass("text-danger"); webhook.set("last_delivery_status", 4);
await rerender();
assert.dom(".status-label").hasClass("--inactive");
}); });
}); });

View File

@ -3,7 +3,8 @@
$mobile-breakpoint: 700px; $mobile-breakpoint: 700px;
:root { :root {
--space-1: 0.25rem; --space-0: 0.125rem; //2px
--space-1: 0.25rem; //4px
--space-2: calc(0.25rem * 2); --space-2: calc(0.25rem * 2);
--space-3: calc(0.25rem * 3); --space-3: calc(0.25rem * 3);
--space-4: calc(0.25rem * 4); --space-4: calc(0.25rem * 4);

View File

@ -67,25 +67,27 @@
} }
} }
// Default
.status-label { .status-label {
--d-border-radius: var(--space-4); --d-border-radius: var(--space-4);
--status-icon-diameter: 8px;
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
width: fit-content; width: fit-content;
background-color: var(--primary-low); background-color: var(--primary-low);
padding: var(--space-1) var(--space-2); padding: var(--space-0) var(--space-2);
border-radius: var(--d-border-radius); border-radius: var(--d-border-radius);
.status-label-indicator { .status-label-indicator {
display: inline-block; display: inline-block;
width: 6px; width: var(--status-icon-diameter);
height: 6px; height: var(--status-icon-diameter);
border-radius: 50%; border-radius: 50%;
background-color: var(--primary-high); background-color: var(--primary-high);
flex-shrink: 0; flex-shrink: 0;
margin-right: var(--space-1); margin-right: var(--space-1);
margin-top: 0.4rem; margin-top: 0.35rem;
} }
.status-label-text { .status-label-text {
@ -93,6 +95,45 @@
font-size: var(--font-down-1); font-size: var(--font-down-1);
} }
} }
// Success badge
.status-label.--success {
background-color: var(--success-low);
.status-label-indicator {
background-color: var(--success);
}
.status-label-text {
color: var(--success-hover);
}
}
// Critical badge
.status-label.--critical {
background-color: var(--danger-low);
.status-label-indicator {
background-color: var(--danger);
}
.status-label-text {
color: var(--danger-hover);
}
}
// Inactive badge
.status-label.--inactive {
background-color: var(--primary-low);
.status-label-indicator {
background-color: var(--primary-high);
}
.status-label-text {
color: var(--primary-high);
}
}
} }
.d-admin-row__overview { .d-admin-row__overview {

View File

@ -1,45 +1,25 @@
// Styles for admin/api // Styles for admin/api
table.web-hooks.grid { .d-admin-table.web-hooks {
td.delivery-status { .d-admin-row__overview.payload-url {
div {
display: flex;
align-items: center;
}
.d-icon {
margin-right: 0.25em;
}
}
td.payload-url {
word-wrap: break-word; word-wrap: break-word;
max-width: 55vw; max-width: 20vw;
@include breakpoint(medium) {
max-width: 70vw;
} }
td.controls {
display: flex;
button {
margin-left: 0.25em;
}
}
@media screen and (min-width: 550px) {
tbody {
tr {
grid-template-columns: 0.5fr repeat(2, 1fr) 0.5fr;
} }
td.controls { .d-admin-row__detail.description {
text-align: right; @include breakpoint(medium) {
display: block;
} }
.d-admin-row__mobile-label {
@include breakpoint(medium) {
display: block;
} }
} }
@include breakpoint(mobile-extra-large) {
tbody {
tr {
grid-template-columns: 0.5fr 1fr;
}
}
td.controls {
grid-row: 2;
}
} }
} }