Skip to content
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

MWPW-154980 - Milo advanced page publishing part 2 #2885

Open
wants to merge 18 commits into
base: stage
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
60 changes: 45 additions & 15 deletions libs/blocks/preflight/panels/general.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { html, signal, useEffect } from '../../../deps/htm-preact.js';
import userCanPublishPage from '../../../tools/utils/publish.js';

const NOT_FOUND = { preview: { lastModified: 'Not found' }, live: { lastModified: 'Not found' } };

Expand All @@ -16,19 +17,19 @@ async function getStatus(url) {
const resp = await fetch(adminUrl);
if (!resp.ok) return {};
const json = await resp.json();
const publish = await userCanPublishPage(json, false);
const preview = json.preview.lastModified || 'Never';
const live = json.live.lastModified || 'Never';
const edit = json.edit.url;
return { url, edit, preview, live };
return { url, edit, preview, live, publish };
}

function getStatuses() {
Object.keys(content.value).forEach((key) => {
content.value[key].items.forEach((item, idx) => {
getStatus(item.url).then((status) => {
content.value[key].items[idx] = status;
content.value = { ...content.value };
});
async function getStatuses() {
Object.keys(content.value).forEach(async (key) => {
content.value[key].items.forEach(async (item, idx) => {
const status = await getStatus(item.url);
content.value[key].items[idx] = status;
content.value = { ...content.value };
});
});
}
Expand Down Expand Up @@ -68,12 +69,17 @@ async function setContent() {
};

getStatuses();
const sk = document.querySelector('helix-sidekick');
sk?.addEventListener('statusfetched', async () => {
getStatuses();
});
}

async function handleAction(action) {
Object.keys(content.value).map(async (key) => {
content.value[key].items.forEach(async (item, idx) => {
if (!item.checked) return;
const checkPublish = action === 'live' ? (item.publish && !item.publish.canPublish) : false;
if (!item.checked || checkPublish) return;
content.value[key].items[idx].action = action;
content.value = { ...content.value };
const adminUrl = getAdminUrl(item.url, action);
Expand Down Expand Up @@ -128,7 +134,19 @@ function prettyPath(url) {
return url.hash ? `${url.pathname} (${url.hash})` : url.pathname;
}

function usePublishProps(item) {
let disablePublish;
if (item.publish && !item.publish.canPublish) {
disablePublish = html`${item.publish.message}`;
}
return {
publishText: html`${item.action === 'live' ? 'Publishing' : prettyDate(item.live)}`,
disablePublish,
};
}

function Item({ name, item, idx }) {
const { publishText, disablePublish } = usePublishProps(item);
const isChecked = item.checked ? ' is-checked' : '';
const isFetching = item.edit ? '' : ' is-fetching';
if (!item.url) return undefined;
Expand All @@ -139,7 +157,9 @@ function Item({ name, item, idx }) {
<p><a href=${item.url.pathname} target=_blank>${prettyPath(item.url)}</a></p>
<p>${item.edit && html`<a href=${item.edit} class=preflight-edit target=_blank>EDIT</a>`}</p>
<p class=preflight-date-wrapper>${item.action === 'preview' ? 'Previewing' : prettyDate(item.preview)}</p>
<p class=preflight-date-wrapper>${item.action === 'live' ? 'Publishing' : prettyDate(item.live)}</p>
<p class="preflight-date-wrapper">
${isChecked && disablePublish ? html`<span class=disabled-publish>${disablePublish}</span>` : publishText}
</p>
</div>`;
}

Expand Down Expand Up @@ -167,12 +187,18 @@ function ContentGroup({ name, group }) {
export default function General() {
useEffect(() => { setContent(); }, []);

const checked = Object.keys(content.value)
.find((key) => content.value[key].items.find((item) => item.checked));
const allChecked = Object.values(content.value)
.flatMap((item) => item.items).filter((item) => item.checked);

const checked = !!allChecked.length;
const publishable = allChecked
.filter((item) => item.checked && !!item.publish?.canPublish).length;

const hasPage = content.value.page;
const selectStyle = checked ? 'Select none' : 'Select all';

const tooltip = allChecked.length !== publishable && 'Puplishing disabled pages will be ignored';

return html`
<div class=preflight-general-content>
${Object.keys(content.value).map((key) => html`<${ContentGroup} name=${key} group=${content.value[key]} />`)}
Expand All @@ -188,9 +214,13 @@ export default function General() {
<div id=preview-action class=preflight-action-wrapper>
<button class=preflight-action onClick=${() => handleAction('preview')}>Preview</button>
</div>
<div id=publish-action class=preflight-action-wrapper>
<button class=preflight-action onClick=${() => handleAction('live')}>Publish</button>
</div>
${!!publishable && html`
<div id=publish-action class="preflight-action-wrapper${tooltip ? ' tooltip' : ''}" data-tooltip=${tooltip}>
<button class="preflight-action" onClick=${() => handleAction('live')}>
Publish
</button>
</div>
`}
`}
</div>
`;
Expand Down
56 changes: 54 additions & 2 deletions libs/blocks/preflight/preflight.css
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ p.preflight-date-wrapper {
margin: 0;
}

p.preflight-date-wrapper .disabled-publish {
font-size: 0.8em;
}

.preflight-group-row p {
margin: 0;
line-height: 1;
Expand Down Expand Up @@ -238,6 +242,9 @@ span.preflight-time {
}

.preflight-action {
display: flex;
align-items: center;
cursor: pointer;
background: var(--action-color);
color: #FFF;
font-weight: 700;
Expand All @@ -251,6 +258,51 @@ span.preflight-time {
clip-path: polygon(0% 0%, var(--notch-size) 0%, calc(100% - var(--notch-size)) 0%, 100% var(--notch-size), 100% calc(100% - var(--notch-size)), 100% 100%, 0% 100%, 0% 100%);
}

.tooltip {
position: relative;
}

.tooltip::before {
content: attr(data-tooltip);
position: absolute;
right: 74%;
transform: translateX(50%);
bottom: 130%;
margin-top: 15px;
width: max-content;
max-width: 157px;
padding: 10px;
border-radius: 4px;
background: white;
color: var(--action-color);
text-align: center;
font-size: 14px;
font-weight: 400;
opacity: 0;
transition: opacity 0.3s, visibility 0.3s;
visibility: hidden;
line-height: 19px;
}

.tooltip::after {
content: '';
bottom: 103%;
right: 69%;
position: absolute;
margin: 12px 1px 0;
border: 5px solid white;
border-color: white transparent transparent;
opacity: 0;
transition: 200ms;
visibility: hidden;
}

.tooltip:hover::before,
.tooltip:hover::after {
opacity: 1;
visibility: visible;
z-index: 9;
}

/* SEO */
.seo-columns {
Expand Down Expand Up @@ -346,7 +398,7 @@ span.preflight-time {
.dialog-modal#preflight .problem-links table td a {
color: #fff;
display: inline-block;
overflow-x: scroll;
overflow-x: scroll;
position: absolute;
top: 50%;
left: 0;
Expand Down Expand Up @@ -566,4 +618,4 @@ img[data-alt-check]::after {

.dialog-modal#preflight table td h3 {
font-size: 16px;
}
}
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;
Loading
Loading