Merge pull request #161 from umbraco/feature/published-dashboard

Feature/published dashboard
This commit is contained in:
Jacob Overgaard
2022-09-23 14:54:49 +02:00
committed by GitHub
12 changed files with 264 additions and 9 deletions

View File

@@ -105,6 +105,35 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
/published-cache/status:
get:
operationId: PublishedCacheStatus
responses:
'200':
description: 200 response
content:
application/json:
schema:
type: string
default:
description: default response
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
/published-cache/reload:
post:
operationId: PublishedCacheReload
parameters: []
responses:
'201':
description: 201 response
'400':
description: 400 response
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
/server/status:
get:
operationId: GetStatus

View File

@@ -22,6 +22,12 @@ export interface paths {
"/manifests/packages/installed": {
get: operations["ManifestsPackagesInstalled"];
};
"/published-cache/status": {
get: operations["PublishedCacheStatus"];
};
"/published-cache/reload": {
post: operations["PublishedCacheReload"];
};
"/server/status": {
get: operations["GetStatus"];
};
@@ -418,6 +424,35 @@ export interface operations {
};
};
};
PublishedCacheStatus: {
responses: {
/** 200 response */
200: {
content: {
"application/json": string;
};
};
/** default response */
default: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
PublishedCacheReload: {
parameters: {};
responses: {
/** 201 response */
201: unknown;
/** 400 response */
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
GetStatus: {
responses: {
/** 200 response */

View File

@@ -1,15 +1,107 @@
import { UUIButtonState } from '@umbraco-ui/uui';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import { customElement, state } from 'lit/decorators.js';
import { getPublishedCacheStatus, postPublishedCacheReload } from '../../../core/api/fetcher';
import { UmbContextConsumerMixin } from '../../../core/context';
import { UmbNotificationService } from '../../../core/services/notification';
import { UmbNotificationDefaultData } from '../../../core/services/notification/layouts/default';
@customElement('umb-dashboard-published-status')
export class UmbDashboardPublishedStatusElement extends LitElement {
static styles = [UUITextStyles, css``];
export class UmbDashboardPublishedStatusElement extends UmbContextConsumerMixin(LitElement) {
static styles = [UUITextStyles, css`
uui-box + uui-box {
margin-top: var(--uui-size-space-5);
}
uui-box p:first-child {
margin-block-start: 0;
}
`];
@state()
private _publishedStatusText = '';
@state()
private _buttonState: UUIButtonState = undefined;
private _notificationService?: UmbNotificationService;
constructor() {
super();
this.consumeContext('umbNotificationService', (notificationService: UmbNotificationService) => {
this._notificationService = notificationService;
});
}
connectedCallback() {
super.connectedCallback();
this._getPublishedStatus();
}
private async _getPublishedStatus() {
try {
const { data } = await getPublishedCacheStatus({});
this._publishedStatusText = data;
} catch (e) {
if (e instanceof getPublishedCacheStatus.Error) {
const error = e.getActualType();
const data: UmbNotificationDefaultData = { message: error.data.detail ?? 'Something went wrong' };
this._notificationService?.peek('danger', { data });
}
}
}
private async _onRefreshCacheHandler() {
this._buttonState = 'waiting';
try {
await postPublishedCacheReload({});
this._getPublishedStatus();
this._buttonState = 'success';
} catch (e) {
this._buttonState = 'failed';
if (e instanceof postPublishedCacheReload.Error) {
const error = e.getActualType();
const data: UmbNotificationDefaultData = { message: error.data.detail ?? 'Something went wrong' };
this._notificationService?.peek('danger', { data });
}
}
}
private async _onReloadCacheHandler() {
await undefined;
}
private async _onRebuildCacheHandler() {
await undefined;
}
private async _onSnapshotCacheHandler() {
await undefined;
}
render() {
return html`
<uui-box>
<h1>Published Status</h1>
<uui-box headline="Published Cache Status">
<p>${this._publishedStatusText}</p>
<uui-button .state=${this._buttonState} type="button" look="primary" @click=${this._onRefreshCacheHandler}>Refresh Status</uui-button>
</uui-box>
<uui-box headline="Memory Cache">
<p>This button lets you reload the in-memory cache, by entirely reloading it from the database cache (but it does not rebuild that database cache). This is relatively fast. Use it when you think that the memory cache has not been properly refreshed, after some events triggered—which would indicate a minor Umbraco issue. (note: triggers the reload on all servers in an LB environment).</p>
<uui-button type="button" look="danger" @click=${this._onReloadCacheHandler}>Reload Memory Cache</uui-button>
</uui-box>
<uui-box headline="Database Cache">
<p>This button lets you rebuild the database cache, ie the content of the cmsContentNu table. Rebuilding can be expensive. Use it when reloading is not enough, and you think that the database cache has not been properly generated—which would indicate some critical Umbraco issue.</p>
<uui-button type="button" look="danger" @click=${this._onRebuildCacheHandler}>Rebuild Database Cache</uui-button>
</uui-box>
<uui-box headline="Internal Cache">
<p>This button lets you trigger a NuCache snapshots collection (after running a fullCLR GC). Unless you know what that means, you probably do not need to use it.</p>
<uui-button type="button" look="danger" @click=${this._onSnapshotCacheHandler}>Snapshot Internal Cache</uui-button>
</uui-box>
`;
}

View File

@@ -0,0 +1,20 @@
import { expect, fixture, html } from '@open-wc/testing';
import { defaultA11yConfig } from '../../../core/helpers/chai';
import { UmbDashboardPublishedStatusElement } from './dashboard-published-status.element';
describe('UmbDashboardPublishedStatus', () => {
let element: UmbDashboardPublishedStatusElement;
beforeEach(async () => {
element = await fixture(html`<umb-dashboard-published-status></umb-dashboard-published-status>`);
});
it('is defined with its own instance', () => {
expect(element).to.be.instanceOf(UmbDashboardPublishedStatusElement);
});
it('passes the a11y audit', async () => {
await expect(element).to.be.accessible(defaultA11yConfig);
});
});

View File

@@ -31,6 +31,10 @@ export class UmbSectionDashboards extends UmbContextConsumerMixin(LitElement) {
padding: var(--uui-size-space-5);
display: block;
}
#scroll-container {
height: 500px;
}
`,
];
@@ -146,7 +150,9 @@ export class UmbSectionDashboards extends UmbContextConsumerMixin(LitElement) {
render() {
return html`
${this._renderNavigation()}
<router-slot id="router-slot" .routes="${this._routes}"></router-slot>
<uui-scroll-container id="scroll-container">
<router-slot id="router-slot" .routes="${this._routes}"></router-slot>
</uui-scroll-container>
`;
}
}

View File

@@ -21,3 +21,5 @@ export const getUpgradeSettings = fetcher.path('/upgrade/settings').method('get'
export const PostUpgradeAuthorize = fetcher.path('/upgrade/authorize').method('post').create();
export const getManifests = fetcher.path('/manifests').method('get').create();
export const getPackagesInstalled = fetcher.path('/manifests/packages/installed').method('get').create();
export const getPublishedCacheStatus = fetcher.path('/published-cache/status').method('get').create();
export const postPublishedCacheReload = fetcher.path('/published-cache/reload').method('post').create();

View File

@@ -1,12 +1,13 @@
import { handlers as contentHandlers } from './domains/node.handlers';
import { handlers as dataTypeHandlers } from './domains/data-type.handlers';
import { handlers as documentTypeHandlers } from './domains/document-type.handlers';
import { handlers as treeHandlers } from './domains/entity.handlers';
import { handlers as installHandlers } from './domains/install.handlers';
import * as manifestsHandlers from './domains/manifests.handlers';
import { handlers as contentHandlers } from './domains/node.handlers';
import { handlers as publishedStatusHandlers } from './domains/published-status.handlers';
import * as serverHandlers from './domains/server.handlers';
import { handlers as upgradeHandlers } from './domains/upgrade.handlers';
import { handlers as userHandlers } from './domains/user.handlers';
import { handlers as treeHandlers } from './domains/entity.handlers';
const handlers = [
serverHandlers.serverVersionHandler,
@@ -18,6 +19,7 @@ const handlers = [
...documentTypeHandlers,
...treeHandlers,
...manifestsHandlers.default,
...publishedStatusHandlers,
];
switch (import.meta.env.VITE_UMBRACO_INSTALL_STATUS) {

View File

@@ -0,0 +1,24 @@
import { rest } from 'msw';
import umbracoPath from '../../core/helpers/umbraco-path';
export const handlers = [
rest.get(umbracoPath('/published-cache/status'), (_req, res, ctx) => {
return res(
// Respond with a 200 status code
ctx.status(200),
ctx.json<string>(
'Database cache is ok. ContentStore contains 1 item and has 1 generation and 0 snapshot. MediaStore contains 5 items and has 1 generation and 0 snapshot.'
)
);
}),
rest.post(umbracoPath('/published-cache/reload'), async (_req, res, ctx) => {
await new Promise((resolve) => setTimeout(resolve, (Math.random() + 1) * 1000)); // simulate a delay of 1-2 seconds
return res(
// Respond with a 200 status code
ctx.status(201)
);
}),
];

View File

@@ -1,8 +1,9 @@
import { handlers as contentHandlers } from './domains/node.handlers';
import { handlers as dataTypeHandlers } from './domains/data-type.handlers';
import { handlers as documentTypeHandlers } from './domains/document-type.handlers';
import { handlers as installHandlers } from './domains/install.handlers';
import * as manifestsHandlers from './domains/manifests.handlers';
import { handlers as contentHandlers } from './domains/node.handlers';
import { handlers as publishedStatusHandlers } from './domains/published-status.handlers';
import * as serverHandlers from './domains/server.handlers';
import { handlers as upgradeHandlers } from './domains/upgrade.handlers';
import { handlers as userHandlers } from './domains/user.handlers';
@@ -18,4 +19,5 @@ export const handlers = [
...dataTypeHandlers,
...documentTypeHandlers,
...manifestsHandlers.default,
...publishedStatusHandlers,
];

View File

@@ -118,6 +118,18 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
weight: 10,
},
},
{
type: 'dashboard',
alias: 'Umb.Dashboard.PublishedStatus',
name: 'Published Status',
elementName: 'umb-dashboard-published-status',
loader: () => import('./backoffice/dashboards/published-status/dashboard-published-status.element'),
meta: {
sections: ['Umb.Section.Settings'],
pathname: 'published-status', // TODO: how to we want to support pretty urls?
weight: 9,
},
},
{
type: 'dashboard',
alias: 'Umb.Dashboard.MediaManagement',

View File

@@ -1,5 +1,6 @@
import './installer';
import './manifests';
import './publishedstatus';
import './server';
import './upgrader';
import './user';

View File

@@ -0,0 +1,30 @@
import { body, defaultResponse, endpoint, request, response } from '@airtasker/spot';
import { ProblemDetails } from './models';
@endpoint({
method: 'GET',
path: '/published-cache/status',
})
export class PublishedCacheStatus {
@response({ status: 200 })
success(@body body: string) {}
@defaultResponse
default(@body body: ProblemDetails) {}
}
@endpoint({
method: 'POST',
path: '/published-cache/reload',
})
export class PublishedCacheReload {
@request
request() {}
@response({ status: 201 })
success() {}
@response({ status: 400 })
badRequest(@body body: ProblemDetails) {}
}