Skip to content

Commit e22db3b

Browse files
Merge branch 'main' into test-updates
2 parents cc21ab9 + 32e253c commit e22db3b

File tree

114 files changed

+4091
-3544
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+4091
-3544
lines changed

concourse/update-content/script.bash

+11-5
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@ fi
1111
# this is here so the creds don't get pasted to the output
1212
set -e; if [ -n "$DEBUG" ]; then set -x; fi
1313

14-
approved_books_default_branch=$(curl -s https://api.github.com/repos/openstax/content-manager-approved-books | jq -r .default_branch)
1514
rex_default_branch=$(curl -s https://api.github.com/repos/openstax/rex-web | jq -r .default_branch)
1615

17-
data=$(curl -sL "https://github.com/openstax/content-manager-approved-books/raw/$approved_books_default_branch/approved-book-list.json")
18-
19-
# script will return a JSON object with book ids and new versions only for books that doesn't mach current config
20-
book_ids_and_versions=$(node script/entry.js transform-approved-books-data --data "$data")
16+
# consumer: Limit entries to those where consumer == <consumer>
17+
# code_version: Limit entries to those where code_version <= <code_version>
18+
archive_version="$(jq -r '.REACT_APP_ARCHIVE' "src/config.archive-url.json")"
19+
search="consumer=REX&code_version=$archive_version"
20+
abl_url="https://corgi.ce.openstax.org/api/abl/?$search"
21+
22+
book_ids_and_versions="$(
23+
bash script/transform-approved-books-data.bash \
24+
<(curl -sL --fail --show-error "$abl_url") \
25+
"src/config.books.json"
26+
)"
2127
book_entries=$(echo "$book_ids_and_versions" | jq -c 'to_entries | .[]')
2228

2329
git remote set-branches origin 'update-content-*'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"bookId": "7193a423-3ad7-4ac6-98dd-90c70e4724b7",
4+
"pageId": "5ce4d68c-f5a2-4e6f-92ed-87da9ba26264",
5+
"pathname": "/books/contemporary-mathematics/pages/5-6-quadratic-equations-with-two-variables-with-applications"
6+
}
7+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"bookId": "d50f6e32-0fda-46ef-a362-9bd36ca7c97d",
4+
"pageId": "a72e87ba-5cfb-4899-b11e-60d7c25ae36a",
5+
"pathname": "/books/university-physics-volume-1/pages/4-4-uniform-circular-motion"
6+
}
7+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"bookId": "f021395f-fd63-46cd-ab95-037c6f051730",
4+
"pageId": "2b4014ea-82b6-49ea-8c26-47ed38be3f34",
5+
"pathname": "/books/precalculus-2e/pages/7-1-solving-trigonometric-equations-with-identities"
6+
}
7+
]

script/prerender/constants.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import config from '../../src/config';
2+
import { assertDefined } from '../../src/app/utils';
3+
4+
export const RELEASE_ID = assertDefined(config.RELEASE_ID, 'REACT_APP_RELEASE_ID environment variable must be set');
5+
export const BUCKET_NAME = process.env.BUCKET_NAME || 'sandbox-unified-web-primary';
6+
export const BUCKET_REGION = process.env.BUCKET_REGION || 'us-east-1';
7+
export const PUBLIC_URL = process.env.PUBLIC_URL || `/rex/releases/${RELEASE_ID}`;
8+
export const WORK_REGION = process.env.WORK_REGION || 'us-east-2';

script/prerender/contentManifest.ts

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { BookWithOSWebData, ArchiveTreeNode, ArchiveTree } from '../../src/app/content/types';
2+
import { content } from '../../src/app/content/routes';
3+
import { writeAssetFile } from './fileUtils';
4+
import { stripIdVersion } from '../../src/app/content/utils/idUtils';
5+
import { splitTitleParts } from '../../src/app/content/utils/archiveTreeUtils';
6+
7+
const quoteValue = (value?: string) => value ? `"${value.replace(/"/g, '""')}"` : '""';
8+
9+
export const renderAndSaveContentManifest = async(
10+
saveFile: (path: string, contents: string) => Promise<unknown>,
11+
books: BookWithOSWebData[]
12+
) => {
13+
14+
const rows = books.map(book => getContentsRows(book, book.tree))
15+
.reduce((result, item) => ([...result, ...item]), [] as string[][]);
16+
17+
const manifestText = [
18+
['id', 'title', 'text title', 'language', 'slug', 'url', 'toc type', 'toc target type'],
19+
...rows,
20+
].map(row => row.map(quoteValue).join(',')).join('\n');
21+
22+
await saveFile('/rex/content-metadata.csv', manifestText);
23+
};
24+
25+
function getContentsRows(
26+
book: BookWithOSWebData,
27+
node: ArchiveTree | ArchiveTreeNode,
28+
chapterNumber?: string
29+
): string[][] {
30+
const {title, toc_target_type} = node;
31+
const [titleNumber, titleString] = splitTitleParts(node.title);
32+
const textTitle = `${titleNumber || chapterNumber || ''} ${titleString}`.replace(/\s+/, ' ').trim();
33+
const id = stripIdVersion(node.id);
34+
const tocType = node.toc_type ?? (id === book.id ? 'book' : '');
35+
36+
const urlParams = tocType === 'book'
37+
? [node.slug, '']
38+
: 'contents' in node
39+
? ['', '']
40+
: [node.slug, content.getUrl({book: {slug: book.slug}, page: {slug: node.slug}})];
41+
42+
const contents = 'contents' in node
43+
? node.contents.map(child => getContentsRows(book, child, titleNumber || chapterNumber))
44+
.reduce((result, item) => ([...result, ...item]), [] as string[][])
45+
: [];
46+
47+
return [
48+
[stripIdVersion(id), title, textTitle, book.language, ...urlParams, tocType, toc_target_type ?? ''],
49+
...contents,
50+
];
51+
}
52+
53+
54+
// simple helper for local
55+
const writeAssetFileAsync = async(filepath: string, contents: string) => {
56+
return writeAssetFile(filepath, contents);
57+
};
58+
export const renderContentManifest = async(books: BookWithOSWebData[]) => {
59+
return renderAndSaveContentManifest(writeAssetFileAsync, books);
60+
};

script/prerender/fileUtils.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { fromContainerMetadata } from '@aws-sdk/credential-providers';
55
import flow from 'lodash/fp/flow';
66
import once from 'lodash/once';
77
import path from 'path';
8-
import { assertDefined } from '../../src/app/utils';
98
import createCache, { Cache } from '../../src/helpers/createCache';
109
import { directoryExists, readFile, writeFile } from '../../src/helpers/fileUtils';
10+
import { BUCKET_REGION, PUBLIC_URL, BUCKET_NAME } from './constants';
1111

1212
const ASSET_DIR = path.resolve(__dirname, '../../build');
1313
const CACHE_DIR = path.resolve(__dirname, '../../cache');
@@ -51,7 +51,7 @@ export const createDiskCache = <K extends string, V>(prefix: string): Cache<K, V
5151

5252
// Generates a release path for a file without a leading /, used when uploading the release to S3
5353
function prefixReleasePath(filepath: string) {
54-
let basePath = assertDefined(process.env.PUBLIC_URL, 'PUBLIC_URL environment variable not set');
54+
let basePath = PUBLIC_URL;
5555
if (basePath[0] === '/') { basePath = basePath.slice(1); }
5656
return `${basePath}${filepath}`;
5757
}
@@ -61,7 +61,7 @@ const getS3Client = once(async() => {
6161
const credentials = await fromContainerMetadata();
6262

6363
console.log('Initializing S3 client');
64-
return new S3Client({ credentials, region: process.env.BUCKET_REGION });
64+
return new S3Client({ credentials, region: BUCKET_REGION });
6565
});
6666

6767
async function writeS3File(key: string, contents: string, contentType: string) {
@@ -70,7 +70,7 @@ async function writeS3File(key: string, contents: string, contentType: string) {
7070
console.log(`Writing s3 file: /${key}`);
7171
return s3Client.send(new PutObjectCommand({
7272
Body: contents,
73-
Bucket: process.env.BUCKET_NAME,
73+
Bucket: BUCKET_NAME,
7474
CacheControl: 'max-age=0',
7575
ContentType: contentType,
7676
Key: key,

script/prerender/fleet.ts

+14-36
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,21 @@ import path from 'path';
3636
import asyncPool from 'tiny-async-pool';
3737
import { makeUnifiedBookLoader } from '../../src/app/content/utils';
3838
import { assertDefined } from '../../src/app/utils';
39-
import config from '../../src/config';
4039
import BOOKS from '../../src/config.books';
4140
import createArchiveLoader from '../../src/gateways/createArchiveLoader';
4241
import { getBooksConfigSync } from '../../src/gateways/createBookConfigLoader';
4342
import createOSWebLoader from '../../src/gateways/createOSWebLoader';
4443
import { readFile } from '../../src/helpers/fileUtils';
4544
import { globalMinuteCounter, prepareBookPages } from './contentPages';
46-
import { SerializedBookMatch, SerializedPageMatch } from './contentRoutes';
45+
import { SerializedPageMatch } from './contentRoutes';
4746
import createRedirects from './createRedirects';
4847
import './logUnhandledRejectionsAndExit';
4948
import renderManifest from './renderManifest';
50-
import { SitemapPayload } from './sitemap';
51-
52-
const {
53-
ARCHIVE_URL,
54-
CODE_VERSION,
55-
OS_WEB_URL,
56-
REACT_APP_OS_WEB_API_URL,
57-
RELEASE_ID,
58-
} = config;
59-
60-
assertDefined(RELEASE_ID, 'REACT_APP_RELEASE_ID environment variable must be set');
49+
import { SitemapPayload, renderAndSaveSitemapIndex } from './sitemap';
50+
import { writeS3ReleaseXmlFile } from './fileUtils';
51+
import { renderAndSaveContentManifest } from './contentManifest';
52+
import { ARCHIVE_URL, OS_WEB_URL, REACT_APP_OS_WEB_API_URL, CODE_VERSION } from '../../src/config';
53+
import { RELEASE_ID, WORK_REGION, BUCKET_NAME, BUCKET_REGION, PUBLIC_URL } from './constants';
6154

6255
// Increasing this too much can lead to connection issues and greater memory usage in the manager
6356
const MAX_CONCURRENT_BOOKS = 5;
@@ -73,11 +66,6 @@ const PRERENDER_TIMEOUT_SECONDS = 3600;
7366
const WORKERS_STACK_CREATE_TIMEOUT_SECONDS = 300;
7467
const WORKERS_STACK_DELETE_TIMEOUT_SECONDS = WORKERS_STACK_CREATE_TIMEOUT_SECONDS;
7568

76-
const BUCKET_NAME = process.env.BUCKET_NAME || 'sandbox-unified-web-primary';
77-
const BUCKET_REGION = process.env.BUCKET_REGION || 'us-east-1';
78-
const PUBLIC_URL = process.env.PUBLIC_URL || `/rex/releases/${RELEASE_ID}`;
79-
const WORK_REGION = process.env.WORK_REGION || 'us-east-2';
80-
8169
// Docker does not accept forward slashes in the image tag
8270
const IMAGE_TAG = process.env.IMAGE_TAG || `${RELEASE_ID.replace(/\//g, '-')}`;
8371

@@ -86,7 +74,6 @@ const sqsClient = new SQSClient({ region: WORK_REGION });
8674

8775
type PageTask = { payload: SerializedPageMatch, type: 'page' };
8876
type SitemapTask = { payload: SitemapPayload, type: 'sitemap' };
89-
type SitemapIndexTask = { payload: SerializedBookMatch[], type: 'sitemapIndex' };
9077

9178
const booksConfig = getBooksConfigSync();
9279
const archiveLoader = createArchiveLoader({
@@ -288,8 +275,7 @@ async function getQueueUrls(workersStackName: string) {
288275
class Stats {
289276
public pages = 0;
290277
public sitemaps = 0;
291-
public sitemapIndexes = 0;
292-
get total() { return this.pages + this.sitemaps + this.sitemapIndexes; }
278+
get total() { return this.pages + this.sitemaps; }
293279
}
294280

295281
function makePrepareAndQueueBook(workQueueUrl: string, stats: Stats) {
@@ -347,11 +333,7 @@ function makePrepareAndQueueBook(workQueueUrl: string, stats: Stats) {
347333

348334
console.log(`[${book.title}] Sitemap queued`);
349335

350-
// Used in the sitemap index
351-
return {
352-
params: { book: { slug: book.slug } },
353-
state: { bookUid: book.id, bookVersion: book.version },
354-
};
336+
return book;
355337
};
356338
}
357339

@@ -371,14 +353,10 @@ async function queueWork(workQueueUrl: string) {
371353
`All ${stats.pages} page prerendering jobs and all ${stats.sitemaps} sitemap jobs queued`
372354
);
373355

374-
await sendWithRetries(sqsClient, new SendMessageCommand({
375-
MessageBody: JSON.stringify({ payload: books, type: 'sitemapIndex' } as SitemapIndexTask),
376-
QueueUrl: workQueueUrl,
377-
}));
378-
379-
stats.sitemapIndexes = 1;
380-
381-
console.log('1 sitemap index job queued');
356+
await Promise.all([
357+
renderAndSaveSitemapIndex(writeS3ReleaseXmlFile, books),
358+
renderAndSaveContentManifest(writeS3ReleaseXmlFile, books),
359+
]);
382360

383361
return stats;
384362
}
@@ -463,8 +441,8 @@ async function finishRendering(stats: Stats) {
463441
const elapsedMinutes = globalMinuteCounter();
464442

465443
console.log(
466-
`Prerender complete in ${elapsedMinutes} minutes. Rendered ${stats.pages} pages, ${
467-
stats.sitemaps} sitemaps and ${stats.sitemapIndexes} sitemap index. ${
444+
`Prerender complete in ${elapsedMinutes} minutes. Rendered ${stats.pages} pages, and ${
445+
stats.sitemaps} sitemaps. ${
468446
stats.total / elapsedMinutes}ppm`
469447
);
470448
}

script/prerender/local.ts

+3-8
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import { ArchivePage, VersionedArchiveBookWithConfig } from '../../src/app/conte
55
import config from '../../src/config';
66
import createArchiveLoader from '../../src/gateways/createArchiveLoader';
77
import createBookConfigLoader from '../../src/gateways/createBookConfigLoader';
8-
import createBuyPrintConfigLoader from '../../src/gateways/createBuyPrintConfigLoader';
9-
import { BuyPrintResponse } from '../../src/gateways/createBuyPrintConfigLoader';
108
import createHighlightClient from '../../src/gateways/createHighlightClient';
119
import createOSWebLoader from '../../src/gateways/createOSWebLoader';
1210
import { OSWebBook } from '../../src/gateways/createOSWebLoader';
@@ -27,9 +25,9 @@ import { createDiskCache } from './fileUtils';
2725
import renderManifest from './renderManifest';
2826
import { renderSitemap, renderSitemapIndex } from './sitemap';
2927
import userLoader from './stubbedUserLoader';
28+
import { renderContentManifest } from './contentManifest';
3029

3130
const {
32-
REACT_APP_BUY_PRINT_CONFIG_URL,
3331
REACT_APP_HIGHLIGHTS_URL,
3432
REACT_APP_OS_WEB_API_URL,
3533
REACT_APP_SEARCH_URL,
@@ -59,17 +57,13 @@ async function render() {
5957
});
6058
const searchClient = createSearchClient(`http://localhost:${port}${REACT_APP_SEARCH_URL}`);
6159
const highlightClient = createHighlightClient(`http://localhost:${port}${REACT_APP_HIGHLIGHTS_URL}`);
62-
const buyPrintConfigLoader = createBuyPrintConfigLoader(REACT_APP_BUY_PRINT_CONFIG_URL, {
63-
cache: createDiskCache<string, BuyPrintResponse>('buy-print'),
64-
});
6560
const practiceQuestionsLoader = createPracticeQuestionsLoader();
6661
const bookConfigLoader = createBookConfigLoader();
6762

6863
const {server} = await startServer({port, onlyProxy: true});
6964
const renderHelpers = {
7065
archiveLoader,
7166
bookConfigLoader,
72-
buyPrintConfigLoader,
7367
config,
7468
highlightClient,
7569
osWebLoader,
@@ -88,7 +82,8 @@ async function render() {
8882
await renderSitemap(book.slug, sitemap);
8983
}
9084

91-
await renderSitemapIndex();
85+
await renderSitemapIndex(books);
86+
await renderContentManifest(books);
9287
await renderManifest();
9388
await createRedirects(archiveLoader, osWebLoader);
9489

script/prerender/sitemap.ts

+9-26
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import filter from 'lodash/fp/filter';
2-
import flow from 'lodash/fp/flow';
3-
import get from 'lodash/fp/get';
4-
import identity from 'lodash/fp/identity';
5-
import map from 'lodash/fp/map';
6-
import max from 'lodash/fp/max';
71
import sitemap, { SitemapItemOptions } from 'sitemap';
82
import { SerializedPageMatch } from './contentRoutes';
93
import { writeAssetFile } from './fileUtils';
4+
import { BookWithOSWebData } from '../../src/app/content/types';
5+
import { getSitemapItemOptions } from './contentPages';
106

117
export const sitemapPath = (pathName: string) => `/rex/sitemaps/${pathName}.xml`;
128

@@ -28,40 +24,27 @@ export const renderAndSaveSitemap = async(
2824

2925
export const renderAndSaveSitemapIndex = async(
3026
saveFile: (path: string, contents: string) => Promise<unknown>,
31-
urls: SitemapItemOptions[]
27+
books: BookWithOSWebData[]
3228
) => {
33-
const sitemapIndex = sitemap.buildSitemapIndex({ urls });
29+
const sitemapIndex = sitemap.buildSitemapIndex({urls: books.map(book =>
30+
getSitemapItemOptions(book, `https://openstax.org${sitemapPath(book.slug)}`)
31+
)});
3432

3533
const filePath = sitemapPath('index');
3634

3735
await saveFile(filePath, sitemapIndex.toString());
38-
39-
return filePath;
4036
};
4137

4238
// renderSitemap() and renderSitemapIndex() are used only by single-instance prerender code
4339

44-
// Multi-instance code cannot store an array of sitemaps in memory and then use it across instances
45-
const sitemaps: SitemapItemOptions[] = [];
46-
4740
const writeAssetFileAsync = async(filepath: string, contents: string) => {
4841
return writeAssetFile(filepath, contents);
4942
};
5043

5144
export const renderSitemap = async(filename: string, urls: SitemapItemOptions[]) => {
52-
const lastmod = flow(
53-
map<SitemapItemOptions, (string | undefined)>(get('lastmod')),
54-
filter<string | undefined>(identity),
55-
max
56-
)(urls);
57-
58-
const filePath = await renderAndSaveSitemap(writeAssetFileAsync, filename, urls);
59-
60-
const url = `https://openstax.org${filePath}`;
61-
62-
sitemaps.push({url, lastmod});
45+
await renderAndSaveSitemap(writeAssetFileAsync, filename, urls);
6346
};
6447

65-
export const renderSitemapIndex = async() => {
66-
return renderAndSaveSitemapIndex(writeAssetFileAsync, sitemaps);
48+
export const renderSitemapIndex = async(books: BookWithOSWebData[]) => {
49+
return renderAndSaveSitemapIndex(writeAssetFileAsync, books);
6750
};

0 commit comments

Comments
 (0)