Skip to content

Commit c510ac1

Browse files
Use custom diff labels; Scrub diff content on overwriteCopy (#179)
Fixes adobe/da-live#747 * Custom diff labels now used for snapshots ("Main" and "Snapshot") * Updates overwriteCopy to remove all da-diff info from source when copying * Use metadata object instead of element for saveToDa and replaceHtml * Add replaceHtml unit test
1 parent c0d3536 commit c510ac1

File tree

4 files changed

+192
-40
lines changed

4 files changed

+192
-40
lines changed

nx/blocks/loc/project/index.js

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -117,31 +117,6 @@ async function saveVersion(path, label) {
117117
return res;
118118
}
119119

120-
export async function overwriteCopy(url, title) {
121-
let blob;
122-
// If source content was supplied upstream, use it.
123-
if (url.sourceContent) {
124-
const type = url.destination.includes('.json') ? 'application/json' : 'text/html';
125-
blob = new Blob([url.sourceContent], { type });
126-
} else {
127-
const srcResp = await daFetch(`${DA_ORIGIN}/source${url.source}`);
128-
if (!srcResp.ok) {
129-
url.status = 'error';
130-
return srcResp;
131-
}
132-
blob = await srcResp.blob();
133-
}
134-
135-
const body = new FormData();
136-
body.append('data', blob);
137-
const opts = { method: 'POST', body };
138-
const daResp = await daFetch(`${DA_ORIGIN}/source${url.destination}`, opts);
139-
url.status = 'success';
140-
// Don't wait for the version save
141-
saveVersion(url.destination, `${title} - Rolled Out`);
142-
return daResp;
143-
}
144-
145120
function collapseInnerTextSpaces(html) {
146121
return html.replace(/>([^<]*)</g, (match, textContent) => {
147122
// Only process if there's actual text content
@@ -173,13 +148,55 @@ const getDaUrl = (url) => {
173148
return { org, repo, pathname };
174149
};
175150

151+
export async function overwriteCopy(url, title) {
152+
let resp;
153+
if (url.sourceContent) {
154+
// If source content was supplied upstream, use it.
155+
const type = url.destination.includes('.json') ? 'application/json' : 'text/html';
156+
const blob = new Blob([url.sourceContent], { type });
157+
const opts = {
158+
method: 'POST',
159+
body: new FormData(),
160+
};
161+
opts.body.append('data', blob);
162+
resp = await daFetch(`${DA_ORIGIN}/source${url.destination}`, opts);
163+
} else {
164+
const srcHtml = await getHtml(url.source);
165+
if (srcHtml) {
166+
removeLocTags(srcHtml);
167+
const daMetadata = getElementMetadata(srcHtml.querySelector(DA_METADATA_SELECTOR));
168+
delete daMetadata?.acceptedhashes;
169+
delete daMetadata?.rejectedhashes;
170+
resp = await saveToDa(
171+
srcHtml.querySelector('main').innerHTML,
172+
getDaUrl(url.destination),
173+
daMetadata,
174+
);
175+
}
176+
}
177+
178+
if (!resp?.ok) {
179+
url.status = 'error';
180+
return null;
181+
}
182+
183+
url.status = 'success';
184+
// Don't wait for the version save
185+
saveVersion(url.destination, `${title} - Rolled Out`);
186+
return resp;
187+
}
188+
176189
function getPreviousHashes(metadata) {
177190
const acceptedHashes = metadata.acceptedhashes?.text?.split(',') || [];
178191
const rejectedHashes = metadata.rejectedhashes?.text?.split(',') || [];
179192
return { acceptedHashes, rejectedHashes };
180193
}
181194

182-
export async function rolloutCopy(url, projectTitle) {
195+
export async function rolloutCopy(
196+
url,
197+
projectTitle,
198+
{ labelLocal = null, labelUpstream = null } = {},
199+
) {
183200
// if the regional folder has content that differs from langstore,
184201
// then a regional diff needs to be done
185202
try {
@@ -203,14 +220,18 @@ export async function rolloutCopy(url, projectTitle) {
203220
}
204221

205222
const daMetadataEl = regionalCopy.querySelector(DA_METADATA_SELECTOR);
206-
const { acceptedHashes, rejectedHashes } = getPreviousHashes(getElementMetadata(daMetadataEl));
223+
const daMetadata = getElementMetadata(daMetadataEl);
224+
const { acceptedHashes, rejectedHashes } = getPreviousHashes(daMetadata);
207225

208226
// There are differences, upload the diffed regional file
209227
const diffed = await regionalDiff(langstoreCopy, regionalCopy, acceptedHashes, rejectedHashes);
210228

229+
if (labelLocal) daMetadata['diff-label-local'] = labelLocal;
230+
if (labelUpstream) daMetadata['diff-label-upstream'] = labelUpstream;
231+
211232
return new Promise((resolve) => {
212233
const daUrl = getDaUrl(url);
213-
const savePromise = saveToDa(diffed.innerHTML, daUrl, { daMetadataEl });
234+
const savePromise = saveToDa(diffed.innerHTML, daUrl, daMetadata);
214235

215236
const timedout = setTimeout(() => {
216237
url.status = 'timeout';
@@ -235,10 +256,17 @@ export async function rolloutCopy(url, projectTitle) {
235256
}
236257
}
237258

238-
export async function mergeCopy(url, projectTitle) {
259+
export async function mergeCopy(
260+
url,
261+
projectTitle,
262+
{ labelLocal = null, labelUpstream = null } = {},
263+
) {
239264
try {
240265
const regionalCopy = await getHtml(url.destination);
241-
if (!regionalCopy) throw new Error('No regional content or error fetching');
266+
const regionalMain = regionalCopy?.querySelector('body > main').innerHTML;
267+
if (!regionalCopy || regionalMain === '' || regionalMain === '<div></div>') {
268+
throw new Error('No regional content or error fetching');
269+
}
242270

243271
const langstoreCopy = url.sourceContent
244272
? await getHtml(null, url.sourceContent)
@@ -255,13 +283,17 @@ export async function mergeCopy(url, projectTitle) {
255283
}
256284

257285
const daMetadataEl = regionalCopy.querySelector(DA_METADATA_SELECTOR);
258-
const { acceptedHashes, rejectedHashes } = getPreviousHashes(getElementMetadata(daMetadataEl));
286+
const daMetadata = getElementMetadata(daMetadataEl);
287+
const { acceptedHashes, rejectedHashes } = getPreviousHashes(daMetadata);
259288

260289
// There are differences, upload the annotated loc file
261290
const diffed = await regionalDiff(langstoreCopy, regionalCopy, acceptedHashes, rejectedHashes);
262291

292+
if (labelLocal) daMetadata['diff-label-local'] = labelLocal;
293+
if (labelUpstream) daMetadata['diff-label-upstream'] = labelUpstream;
294+
263295
const daUrl = getDaUrl(url);
264-
const { daResp } = await saveToDa(diffed.innerHTML, daUrl, { daMetadataEl });
296+
const { daResp } = await saveToDa(diffed.innerHTML, daUrl, daMetadata);
265297
if (daResp.ok) {
266298
url.status = 'success';
267299
saveVersion(url.destination, `${projectTitle} - Rolled Out`);

nx/blocks/snapshot-admin/utils/utils.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ export async function copyManifest(name, resources, direction) {
140140
// The action to take
141141
const copyUrl = async (url) => {
142142
if (url.source.endsWith('.html')) {
143-
await mergeCopy(url, `Snapshot ${direction}`);
143+
const labels = (direction === 'fork')
144+
? { labelLocal: 'Snapshot', labelUpstream: 'Main' }
145+
: { labelLocal: 'Main', labelUpstream: 'Snapshot' };
146+
await mergeCopy(url, `Snapshot ${direction}`, labels);
144147
} else {
145148
await overwriteCopy(url, `Snapshot ${direction}`);
146149
}

nx/utils/daFetch.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const daFetch = async (url, opts = {}) => {
4040
return resp;
4141
};
4242

43-
export function replaceHtml(text, fromOrg, fromRepo, { daMetadataEl = null } = {}) {
43+
export function replaceHtml(text, fromOrg, fromRepo, daMetadata = {}) {
4444
let inner = text;
4545
if (fromOrg && fromRepo) {
4646
const fromOrigin = `https://main--${fromRepo}--${fromOrg}.aem.live`;
@@ -49,22 +49,29 @@ export function replaceHtml(text, fromOrg, fromRepo, { daMetadataEl = null } = {
4949
.replaceAll('href="/', `href="${fromOrigin}/`);
5050
}
5151

52+
let metadataHTML = '';
53+
if (Object.keys(daMetadata).length > 0) {
54+
const daRows = Object.entries(daMetadata)
55+
.map(([key, value]) => `<div><div>${key}</div><div>${value}</div></div>`)
56+
.join('');
57+
metadataHTML = `\n <div class="da-metadata">${daRows}</div>\n`;
58+
}
59+
5260
return `
5361
<body>
5462
<header></header>
5563
<main>${inner}</main>
56-
${daMetadataEl ? `<div class="da-metadata">${daMetadataEl.innerHTML}</div>` : ''}
57-
<footer></footer>
64+
${metadataHTML}<footer></footer>
5865
</body>
5966
`;
6067
}
6168

62-
export async function saveToDa(text, url, { daMetadataEl = null } = {}) {
63-
const daPath = `/${url.org}/${url.repo}${url.pathname}`;
69+
export async function saveToDa(text, url, daMetadata = {}) {
70+
const { org, repo, pathname } = url;
71+
const daPath = `/${org}/${repo}${pathname}`;
6472
const daHref = `https://da.live/edit#${daPath}`;
65-
const { org, repo } = url;
6673

67-
const body = replaceHtml(text, org, repo, { daMetadataEl });
74+
const body = replaceHtml(text, org, repo, daMetadata);
6875

6976
const blob = new Blob([body], { type: 'text/html' });
7077
const formData = new FormData();

test/utils/daFetch.test.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { expect } from '@esm-bundle/chai';
2+
import { replaceHtml } from '../../nx/utils/daFetch.js';
3+
4+
describe('replaceHtml', () => {
5+
describe('structure', () => {
6+
it('wraps content in body/header/main/footer', () => {
7+
const out = replaceHtml('hello');
8+
expect(out).to.include('<body>');
9+
expect(out).to.include('<header></header>');
10+
expect(out).to.include('<main>hello</main>');
11+
expect(out).to.include('<footer></footer>');
12+
});
13+
14+
it('handles empty text', () => {
15+
const out = replaceHtml('');
16+
expect(out).to.include('<main></main>');
17+
});
18+
});
19+
20+
describe('when fromOrg or fromRepo is missing', () => {
21+
it('does not replace ./media or href when both missing', () => {
22+
const text = 'x ./media y href="/foo"';
23+
const out = replaceHtml(text);
24+
expect(out).to.include('<main>x ./media y href="/foo"</main>');
25+
expect(out).not.to.include('aem.live');
26+
});
27+
28+
it('does not replace when only fromOrg is set', () => {
29+
const text = './media href="/bar"';
30+
expect(replaceHtml(text, 'myorg', null)).to.include(`<main>${text}</main>`);
31+
expect(replaceHtml(text, 'myorg', undefined)).to.include(`<main>${text}</main>`);
32+
expect(replaceHtml(text, 'myorg', null)).not.to.include('aem.live');
33+
});
34+
35+
it('does not replace when only fromRepo is set', () => {
36+
const text = './media href="/baz"';
37+
expect(replaceHtml(text, null, 'myrepo')).to.include(`<main>${text}</main>`);
38+
expect(replaceHtml(text, '', 'myrepo')).to.include(`<main>${text}</main>`);
39+
expect(replaceHtml(text, null, 'myrepo')).not.to.include('aem.live');
40+
});
41+
});
42+
43+
describe('when fromOrg and fromRepo are set', () => {
44+
const origin = 'https://main--myrepo--myorg.aem.live';
45+
46+
it('replaces ./media with origin-prefixed /media', () => {
47+
const out = replaceHtml('link ./media/image.png', 'myorg', 'myrepo');
48+
expect(out).to.include(`${origin}/media/image.png`);
49+
expect(out).not.to.include('./media/image.png');
50+
});
51+
52+
it('replaces all ./media occurrences', () => {
53+
const text = './media/a ./media/b';
54+
const out = replaceHtml(text, 'myorg', 'myrepo');
55+
expect(out).to.include(`${origin}/media/a`);
56+
expect(out).to.include(`${origin}/media/b`);
57+
});
58+
59+
it('replaces href="/ with origin-prefixed href', () => {
60+
const out = replaceHtml('href="/page"', 'myorg', 'myrepo');
61+
expect(out).to.include(`href="${origin}/page"`);
62+
expect(out).not.to.include('href="/page"');
63+
});
64+
65+
it('replaces all href="/ occurrences', () => {
66+
const text = 'href="/a" href="/b"';
67+
const out = replaceHtml(text, 'myorg', 'myrepo');
68+
expect(out).to.include(`href="${origin}/a"`);
69+
expect(out).to.include(`href="${origin}/b"`);
70+
});
71+
72+
it('applies both replacements in one string', () => {
73+
const text = 'link ./media/x.png and href="/y"';
74+
const out = replaceHtml(text, 'myorg', 'myrepo');
75+
expect(out).to.include(`${origin}/media/x.png`);
76+
expect(out).to.include(`href="${origin}/y"`);
77+
});
78+
});
79+
80+
describe('daMetadata', () => {
81+
it('adds no da-metadata div when empty object', () => {
82+
const out = replaceHtml('x', null, null, {});
83+
expect(out).not.to.include('da-metadata');
84+
});
85+
86+
it('adds no da-metadata div when not passed', () => {
87+
const out = replaceHtml('x');
88+
expect(out).not.to.include('da-metadata');
89+
});
90+
91+
it('adds da-metadata div with key/value rows when metadata provided', () => {
92+
const out = replaceHtml('x', null, null, { foo: 'bar' });
93+
expect(out).to.include('class="da-metadata"');
94+
expect(out).to.include('<div>foo</div><div>bar</div>');
95+
});
96+
97+
it('adds multiple metadata entries', () => {
98+
const out = replaceHtml('x', null, null, { a: '1', b: '2' });
99+
expect(out).to.include('<div>a</div><div>1</div>');
100+
expect(out).to.include('<div>b</div><div>2</div>');
101+
});
102+
103+
it('combines metadata with url replacement', () => {
104+
const out = replaceHtml('./media/img.png', 'o', 'r', { key: 'val' });
105+
expect(out).to.include('class="da-metadata"');
106+
expect(out).to.include('<div>key</div><div>val</div>');
107+
expect(out).to.include('https://main--r--o.aem.live/media/img.png');
108+
});
109+
});
110+
});

0 commit comments

Comments
 (0)