Skip to content

Commit

Permalink
MWPW-154980 - Milo advanced page publishing (#2846)
Browse files Browse the repository at this point in the history
* sidekick publish button state

* bulk publish publish config errors

* dial in functionality add custom messages and denylist

* publish permission in bulk pub testing

* test permissions and utils coverage

* remove extra variable assignment

* add no signin message

* put back timout for init sidekick pub button decoration

* fix edge case where sk event isnt fired on refresh

* fix unit tests

* put back var assignment

* dial in user can publish funtion

* move publish utility to tools dir

* Update tools/utils/utils.js

Co-authored-by: Ryan Parrish <churchofslidin@gmail.com>

---------

Co-authored-by: Ryan Parrish <churchofslidin@gmail.com>
  • Loading branch information
Sartxi and ryanmparrish authored Sep 12, 2024
1 parent a7c31b7 commit 8fd5925
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 30 deletions.
17 changes: 11 additions & 6 deletions libs/blocks/bulk-publish-v2/components/bulk-publisher.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './job-process.js';
import { LitElement, html } from '../../../deps/lit-all.min.js';
import { getSheet } from '../../../../tools/utils/utils.js';
import { authenticate, startJob } from '../services.js';
import { authenticate, getPublishable, startJob } from '../services.js';
import { getConfig } from '../../../utils/utils.js';
import {
delay,
Expand Down Expand Up @@ -95,7 +95,8 @@ class BulkPublish2 extends LitElement {
this.validateUrls();
}

setJobErrors(errors) {
setJobErrors(jobErrors, authErrors) {
const errors = [...jobErrors, ...authErrors];
const urls = [];
errors.forEach((error) => {
const matched = this.urls.filter((url) => {
Expand Down Expand Up @@ -323,7 +324,8 @@ class BulkPublish2 extends LitElement {
class="panel-title"
@click=${handleToggle}>
<span class="title">
Job Results
${this.jobs.length ? html`<strong>${this.jobs.length}</strong>` : ''}
Job Result${this.jobs.length > 1 ? 's' : ''}
</span>
<div class="jobs-tools${showList}">
<div
Expand Down Expand Up @@ -380,16 +382,17 @@ class BulkPublish2 extends LitElement {
async submit() {
if (!this.isDisabled()) {
this.processing = 'started';
const { authorized, unauthorized } = await getPublishable(this);
const job = await startJob({
urls: this.urls,
urls: authorized,
process: this.process.toLowerCase(),
useBulk: this.user.permissions[this.process]?.useBulk ?? false,
});
const { complete, error } = processJobResult(job);
this.jobs = [...this.jobs, ...complete];
this.processing = complete.length ? 'job' : false;
if (error.length) {
this.setJobErrors(error);
if (error.length || unauthorized.length) {
this.setJobErrors(error, unauthorized);
} else {
if (this.mode === 'full') this.openJobs = true;
this.reset();
Expand All @@ -407,6 +410,7 @@ class BulkPublish2 extends LitElement {

renderPromptLoader() {
setTimeout(() => {
/* c8 ignore next 4 */
const loader = this.renderRoot.querySelector('.load-indicator');
const message = this.renderRoot.querySelector('.message');
loader?.classList.add('hide');
Expand All @@ -427,6 +431,7 @@ class BulkPublish2 extends LitElement {
const canUse = Object.values(this.user.permissions).filter((perms) => perms.canUse);
if (canUse.length) return html``;
message = 'Current user is not authorized to use Bulk Publishing Tool';
/* c8 ignore next 3 */
} else {
message = 'Please sign in to AEM sidekick to continue';
}
Expand Down
25 changes: 25 additions & 0 deletions libs/blocks/bulk-publish-v2/services.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import userCanPublishPage from '../../tools/utils/publish.js';
import {
PROCESS_TYPES,
getErrorText,
Expand Down Expand Up @@ -246,8 +247,32 @@ const updateRetry = async ({ queue, urls, process }) => {
return newQueue;
};

// publish authentication service
const getPublishable = async ({ urls, process, user }) => {
let publishable = { authorized: [], unauthorized: [] };
if (!isLive(process)) {
publishable.authorized = urls;
} else {
const { permissions, profile } = user;
const live = { permissions: ['read'] };
if (permissions?.publish?.canUse) {
live.permissions.push('write');
}
publishable = await urls.reduce(async (init, url) => {
const result = await init;
const detail = { webPath: new URL(url).pathname, live, profile };
const { canPublish, message } = await userCanPublishPage(detail);
if (canPublish) result.authorized.push(url);
else result.unauthorized.push({ href: url, message });
return result;
}, Promise.resolve(publishable));
}
return publishable;
};

export {
authenticate,
getPublishable,
pollJobStatus,
startJob,
updateRetry,
Expand Down
32 changes: 32 additions & 0 deletions libs/tools/utils/publish.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getCustomConfig } from '../../../tools/utils/utils.js';

const userCanPublishPage = async (detail, isBulk = true) => {
if (!detail) return false;
const { live, profile, webPath } = detail;
let canPublish = isBulk ? live?.permissions?.includes('write') : true;
let message = 'Publishing is currently disabled for this page';
const config = await getCustomConfig('/.milo/publish-permissions-config.json');
const item = config?.urls?.data?.find(({ url }) => (
url.endsWith('**') ? webPath.includes(url.slice(0, -2)) : url === webPath
));
if (item) {
canPublish = false;
if (item.message) message = item.message;
const group = config[item.group];
if (group && profile?.email) {
let isDeny;
const user = group.data?.find(({ allow, deny }) => {
if (deny) {
/* c8 ignore next 3 */
isDeny = true;
return deny === profile.email;
}
return allow === profile.email;
});
canPublish = isDeny ? !user : !!user;
}
}
return { canPublish, message };
};

export default userCanPublishPage;
61 changes: 52 additions & 9 deletions libs/utils/sidekick-decorate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
import userCanPublishPage from '../tools/utils/publish.js';

const PUBLISH_BTN = '.publish.plugin button';
const BACKUP_PROFILE = '.profile-email';
const CONFIRM_MESSAGE = 'Are you sure? This will publish to production.';

export default function stylePublish(sk) {
const setupPublishBtn = async (page, btn) => {
const { canPublish, message } = await userCanPublishPage(page, false);
btn.setAttribute('disabled', !canPublish);
const messageText = btn.querySelector('span');
const text = canPublish ? CONFIRM_MESSAGE : message;
if (messageText) {
messageText.innerText = text;
} else {
btn.insertAdjacentHTML('beforeend', `<span>${text}</span>`);
}
};

const style = new CSSStyleSheet();
style.replaceSync(`
:host {
Expand All @@ -10,19 +28,21 @@ export default function stylePublish(sk) {
order: 100;
}
.publish.plugin button {
position: relative;
}
.publish.plugin button:not([disabled=true]) {
background: var(--bg-color);
border-color: #b46157;
color: var(--text-color);
position: relative;
}
.publish.plugin button:hover {
.publish.plugin button:not([disabled=true]):hover {
background-color: var(--hlx-sk-button-hover-bg);
border-color: unset;
color: var(--hlx-sk-button-hover-color);
}
.publish.plugin button > span {
display: none;
background: var(--bg-color);
background: #666;
border-radius: 4px;
line-height: 1.2rem;
padding: 8px 12px;
Expand All @@ -33,6 +53,9 @@ export default function stylePublish(sk) {
width: 150px;
white-space: pre-wrap;
}
.publish.plugin button:not([disabled=true]) > span {
background: var(--bg-color);
}
.publish.plugin button:hover > span {
display: block;
color: var(--text-color);
Expand All @@ -41,19 +64,39 @@ export default function stylePublish(sk) {
content: '';
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid var(--bg-color);
border-bottom: 6px solid #666;
position: absolute;
text-align: center;
top: -6px;
left: 50%;
transform: translateX(-50%);
}
.publish.plugin button:not([disabled=true]) > span:before {
border-bottom: 6px solid var(--bg-color);
}
`);

sk.shadowRoot.adoptedStyleSheets = [style];
setTimeout(() => {
const btn = sk.shadowRoot.querySelector('.publish.plugin button');
btn?.insertAdjacentHTML('beforeend', `
<span>Are you sure? This will publish to production.</span>
`);

sk.addEventListener('statusfetched', async (event) => {
const page = event?.detail?.data;
const btn = event?.target?.shadowRoot?.querySelector(PUBLISH_BTN);
if (page && btn) {
setupPublishBtn(page, btn);
}
});

setTimeout(async () => {
const btn = sk.shadowRoot.querySelector(PUBLISH_BTN);
btn?.setAttribute('disabled', true);
const message = btn?.querySelector('span');
if (btn && !message) {
const page = {
webPath: window.location.pathname,
// added for edge case where the statusfetched event isnt fired on refresh
profile: { email: sk.shadowRoot.querySelector(BACKUP_PROFILE)?.innerText },
};
setupPublishBtn(page, btn);
}
}, 500);
}
33 changes: 21 additions & 12 deletions test/blocks/bulk-publish-v2/bulk-publish-v2.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ describe('Bulk Publish Tool', () => {
await mouseEvent(rootEl.querySelector('.fix-btn'));
});

it('can trigger cannot publish config', async () => {
await clock.runAllAsync();
await setProcess(rootEl, 'publish');
await setTextArea(rootEl, 'https://error--milo--adobecom.hlx.page/not/a/valid/path');
await mouseEvent(rootEl.querySelector('#RunProcess'));
const errors = rootEl.querySelector('.errors');
expect(errors.querySelector('strong').innerText).to.equal('Publishing disabled until the test is over');
await mouseEvent(rootEl.querySelector('.fix-btn'));
});

it('can validate milo urls and enable form', async () => {
await clock.runAllAsync();
await setProcess(rootEl, 'publish');
Expand All @@ -132,6 +142,17 @@ describe('Bulk Publish Tool', () => {
await mouseEvent(rootEl.querySelector('.switch.half'));
});

it('can toggle job timing flyout', async () => {
await clock.runAllAsync();
const doneJobProcess = rootEl.querySelector('job-process');
const jobInfo = doneJobProcess?.shadowRoot.querySelector('job-info');
const timerDetail = jobInfo?.shadowRoot.querySelector('.timer');
await mouseEvent(timerDetail);
await clock.runAllAsync();
await mouseEvent(timerDetail);
expect(timerDetail.classList.contains('show-times')).to.be.false;
});

it('can submit valid bulk preview job', async () => {
await clock.runAllAsync();
await setProcess(rootEl, 'preview');
Expand Down Expand Up @@ -174,17 +195,6 @@ describe('Bulk Publish Tool', () => {
expect(rootEl.querySelectorAll('job-process')).to.have.lengthOf(4);
});

it('can toggle job timing flyout', async () => {
await clock.runAllAsync();
const doneJobProcess = rootEl.querySelector('job-process');
const jobInfo = doneJobProcess?.shadowRoot.querySelector('job-info');
const timerDetail = jobInfo?.shadowRoot.querySelector('.timer');
await mouseEvent(timerDetail);
await clock.runAllAsync();
await mouseEvent(timerDetail);
expect(timerDetail.classList.contains('show-times')).to.be.false;
});

it('can toggle view mode', async () => {
await mouseEvent(rootEl.querySelector('.switch.full'));
await clock.runAllAsync();
Expand Down Expand Up @@ -218,7 +228,6 @@ describe('Bulk Publish Tool', () => {
it('can clear bulk jobs', async () => {
await clock.runAllAsync();
await mouseEvent(rootEl.querySelector('.clear-jobs'));
await clock.runAllAsync();
expect(rootEl.querySelectorAll('job-process')).to.have.lengthOf(0);
});
});
2 changes: 1 addition & 1 deletion test/blocks/bulk-publish-v2/mocks/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class MockAuth extends HTMLElement {
bubbles: true,
detail: {
data: {
profile: { name: 'Unit Test' },
profile: { name: 'Unit Test', email: 'tester@adobe.com' },
preview: { permissions },
live: { permissions },
},
Expand Down
3 changes: 2 additions & 1 deletion test/blocks/bulk-publish-v2/mocks/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const requests = {
delstatus: 'https://admin.hlx.page/job/adobecom/milo/main/preview-remove/job-2024-01-24t23-16-20-377z/details',
retry: 'https://admin.hlx.page/preview/adobecom/milo/main/tools/bulk-publish-v2-test',
index: 'https://admin.hlx.page/index/adobecom/milo/main/tools/bulk-publish-v2-test',
permissions: '/.milo/publish-permissions-config.json',
};

const getMocks = async () => {
Expand All @@ -25,7 +26,7 @@ const getMocks = async () => {
export async function mockFetch() {
const mocks = await getMocks();
stub(window, 'fetch').callsFake((...args) => {
const headers = args[1].body ?? null;
const headers = args[1]?.body ?? null;
const body = headers ? JSON.parse(headers) : false;
const [resource] = args;
const response = mocks.find((mock) => (body.delete ? mock.request === 'delete' : mock.url === resource));
Expand Down
26 changes: 26 additions & 0 deletions test/blocks/bulk-publish-v2/mocks/response/permissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"urls": {
"total": 4,
"offset": 0,
"limit": 4,
"data": [
{
"url": "/not/a/valid/path",
"group": "gwp-test",
"message": "Publishing disabled until the test is over"
}
]
},
"gwp-test": {
"total": 2,
"offset": 0,
"limit": 2,
"data": [
{ "allow": "testuser@adobe.com" },
{ "allow": "testuser1@adobe.com" }
]
},
":version": 3,
":names": ["urls", "gwp-US", "no-publish", "gwp-FR"],
":type": "multi-sheet"
}
14 changes: 13 additions & 1 deletion tools/utils/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const IMS_CLIENT_ID = 'milo_ims';
const IMS_PROD_URL = 'https://auth.services.adobe.com/imslib/imslib.min.js';
const STYLE_SHEETS = {};
const CONFIGS = {};

const getImsToken = async (loadScript) => {
window.adobeid = {
Expand All @@ -25,4 +26,15 @@ const getSheet = async (url) => {
return sheet;
};

export { getImsToken, getSheet };
const getCustomConfig = async (path) => {
/* c8 ignore next 3 */
if (CONFIGS[path] !== undefined) {
return CONFIGS[path];
}
const resp = await fetch(path);
const config = resp.ok ? await resp.json() : null;
CONFIGS[path] = config;
return config;
};

export { getImsToken, getSheet, getCustomConfig };

0 comments on commit 8fd5925

Please sign in to comment.