Skip to content

Commit 0a5c0c3

Browse files
tabathadelaneclarkmcadoopaperclypsesunnyzanchi
authored
Feat: new EOL Comms pages (newrelic#18055)
* feat: create EOL comms UI overview and template page * feat: add sort date option to eol feed * chore: add styling if eol date has passed * feat: create sortable table layout * chore: add PropTypes and clean up console errors * feat: add rss feed for eol posts * fix: remove console log * fix: remove check on function * feat(EOL): Added initial EOL content that's active and announced in Q2 (newrelic#18232) * feat(EOL): Added initial EOL content that's active and announced in Q2 * fix(Docs): Removed DNT tags * fix(Docs): Fixing some minor wording issues * chore(Docs): Removed placeholder content Goodbye Quenya! * fix(Docs): Responded to peer feedback * fix(Docs): Wanted to avoid using EOL in titles (newrelic#18273) got a PR approval from eng * add `<p>` tag for body and right padding * add sort direction * copy updates * chore: add EOL to nav * chore: comment out `<p>` for body * style(EOL): Changed short title to match title --------- Co-authored-by: Clark McAdoo <[email protected]> Co-authored-by: Clark McAdoo <[email protected]> Co-authored-by: Shawn Kilburn <[email protected]> Co-authored-by: Sunny Zanchi <[email protected]>
1 parent d735b15 commit 0a5c0c3

File tree

19 files changed

+975
-13
lines changed

19 files changed

+975
-13
lines changed

gatsby-config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ module.exports = {
321321
'gatsby-plugin-release-note-rss',
322322
'gatsby-plugin-whats-new-rss',
323323
'gatsby-plugin-security-bulletins-rss',
324-
324+
'gatsby-plugin-eol-rss',
325325
'gatsby-source-nav',
326326
'gatsby-source-install-config',
327327
// https://www.gatsbyjs.com/plugins/gatsby-plugin-typegen/

gatsby-node.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,9 @@ const getTemplate = (node) => {
468468
case fileRelativePath.includes('src/content/docs/release-notes'):
469469
return { template: 'releaseNote' };
470470

471+
case fileRelativePath.includes('src/content/eol'):
472+
return { template: 'eolAnnouncement' };
473+
471474
case fileRelativePath.includes('src/content/whats-new'):
472475
return { template: 'whatsNew' };
473476

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const RSS = require('rss');
4+
const format = require('date-fns/format');
5+
const parseISO = require('date-fns/parseISO');
6+
const unified = require('unified');
7+
const parse = require('rehype-parse');
8+
const addAbsoluteImagePath = require('../../rehype-plugins/utils/addAbsoluteImagePath');
9+
const rehypeStringify = require('rehype-stringify');
10+
11+
const eolQuery = async (graphql) => {
12+
const query = `
13+
{
14+
site {
15+
siteMetadata {
16+
title
17+
siteUrl
18+
}
19+
}
20+
allMarkdownRemark(filter: {fields: {slug: {regex: "/^/eol/"}}}) {
21+
nodes {
22+
frontmatter {
23+
title
24+
publishDate
25+
eolEffectiveDate
26+
summary
27+
}
28+
fields {
29+
slug
30+
}
31+
htmlAst
32+
}
33+
}
34+
}
35+
`;
36+
37+
const { data } = await graphql(query);
38+
39+
return data;
40+
};
41+
42+
const htmlParser = unified()
43+
.use(parse)
44+
.use(addAbsoluteImagePath)
45+
.use(rehypeStringify);
46+
47+
const getFeedItem = (node, siteMetadata) => {
48+
const {
49+
frontmatter,
50+
fields: { slug },
51+
htmlAst,
52+
} = node;
53+
const { title, publishDate, summary } = frontmatter;
54+
55+
const parsedHtml = htmlParser.runSync(htmlAst);
56+
57+
// time is necessary for RSS validity
58+
const date = parseISO(publishDate);
59+
const pubDate = `${format(date, 'EE, dd LLL yyyy')} 00:00:00 +0000`;
60+
const link = new URL(slug, siteMetadata.siteUrl).href;
61+
const id = Buffer.from(`${publishDate}-${title}`).toString('base64');
62+
63+
return {
64+
guid: id,
65+
title,
66+
custom_elements: [
67+
{ link },
68+
{ pubDate },
69+
{ 'content:encoded': htmlParser.stringify(parsedHtml) },
70+
{ description: summary ? summary : `ReleasedOn: ${pubDate}.` },
71+
],
72+
};
73+
};
74+
75+
const generateFeed = (publicDir, siteMetadata, reporter, eolNodes) => {
76+
const title = `New Relic EOL`;
77+
78+
let feedPath = path.join('eol', 'feed.xml');
79+
const buildLang = process.env.BUILD_LANG;
80+
81+
// generate the XML at `<lang>/eol/feed.xml` for the i18n sites,
82+
// otherwise they'll 404.
83+
if (buildLang !== 'en') {
84+
feedPath = path.join(buildLang, feedPath);
85+
}
86+
87+
// https://github.com/dylang/node-rss#feedoptions
88+
const feedOptions = {
89+
title,
90+
feed_url: new URL(feedPath, siteMetadata.siteUrl).href,
91+
site_url: siteMetadata.siteUrl,
92+
};
93+
94+
reporter.info(`\t${feedOptions.feed_url}`);
95+
96+
const feed = new RSS(feedOptions);
97+
98+
eolNodes.nodes.map((node) => {
99+
feed.item(getFeedItem(node, siteMetadata));
100+
});
101+
102+
const filepath = path.join(publicDir, feedPath);
103+
const dir = path.dirname(filepath);
104+
105+
if (!fs.existsSync(dir)) {
106+
fs.mkdirSync(dir, { recursive: true });
107+
}
108+
109+
fs.writeFileSync(filepath, feed.xml());
110+
};
111+
112+
exports.onPostBuild = async ({ graphql, store, reporter }) => {
113+
const { program } = store.getState();
114+
const publicDir = path.join(program.directory, 'public');
115+
116+
try {
117+
reporter.info(`Generating XML for EOL RSS feed`);
118+
const { site, allMarkdownRemark } = await eolQuery(graphql);
119+
120+
generateFeed(publicDir, site.siteMetadata, reporter, allMarkdownRemark);
121+
122+
reporter.info('\tDone!');
123+
} catch (error) {
124+
reporter.panicOnBuild(`Unable to create EOL RSS feed: ${error}`, error);
125+
}
126+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

scripts/actions/utils/docs-content-tools/i18n-exclusions.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
excludePath:
22
ja-JP:
33
- src/announcements
4+
- src/content/eol
45
- src/content/whats-new
56
- src/content/docs/release-notes
67
- src/content/docs/licenses
@@ -25,6 +26,7 @@ excludePath:
2526

2627
ko-KR:
2728
- src/announcements
29+
- src/content/eol
2830
- src/content/whats-new
2931
- src/content/docs/release-notes
3032
- src/content/docs/licenses
@@ -49,6 +51,7 @@ excludePath:
4951

5052
es-LA:
5153
- src/announcements
54+
- src/content/eol
5255
- src/content/whats-new
5356
- src/content/docs/release-notes
5457
- src/content/docs/licenses
@@ -73,6 +76,7 @@ excludePath:
7376

7477
pt-BR:
7578
- src/announcements
79+
- src/content/eol
7680
- src/content/whats-new
7781
- src/content/docs/release-notes
7882
- src/content/docs/licenses

scripts/utils/verify-mdx-utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const readFile = async (filePath) => {
8686
}
8787
const excludeFromFreshnessRegex = [
8888
'src/content/docs/release-notes/',
89+
'src/content/eol/',
8990
'src/content/whats-new/',
9091
'src/content/docs/style-guide/',
9192
'src/content/docs/security/new-relic-security/security-bulletins/',

src/components/EolTable/EolTable.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { css } from '@emotion/react';
4+
5+
import TableBody from './TableBody';
6+
import TableHeader from './TableHeader';
7+
8+
const EolTable = ({
9+
body,
10+
headers,
11+
setSortDirection,
12+
setSortField,
13+
sortDirection,
14+
sortField,
15+
}) => {
16+
return (
17+
<>
18+
<table
19+
css={css`
20+
padding-right: 4rem;
21+
width: 100%;
22+
`}
23+
>
24+
<TableHeader
25+
headers={headers}
26+
sortDirection={sortDirection}
27+
sortField={sortField}
28+
setSortDirection={setSortDirection}
29+
setSortField={setSortField}
30+
/>
31+
<TableBody headers={headers} body={body} />
32+
</table>
33+
</>
34+
);
35+
};
36+
37+
EolTable.propTypes = {
38+
headers: PropTypes.arrayOf(PropTypes.object).isRequired,
39+
body: PropTypes.arrayOf(PropTypes.object).isRequired,
40+
sortField: PropTypes.string.isRequired,
41+
setSortField: PropTypes.func.isRequired,
42+
};
43+
44+
export default EolTable;

src/components/EolTable/TableBody.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { css } from '@emotion/react';
4+
5+
const TableBody = ({ body, headers }) => {
6+
return (
7+
<tbody
8+
css={css`
9+
tr {
10+
vertical-align: top;
11+
border: 2px solid grey;
12+
}
13+
`}
14+
>
15+
{body.map((content) => {
16+
return (
17+
<tr key={content.id}>
18+
{headers.map(({ contentId }) => {
19+
return <td key={contentId}>{content[contentId]}</td>;
20+
})}
21+
</tr>
22+
);
23+
})}
24+
</tbody>
25+
);
26+
};
27+
TableBody.propTypes = {
28+
headers: PropTypes.arrayOf(
29+
PropTypes.shape({
30+
label: PropTypes.string.isRequired,
31+
contentId: PropTypes.string.isRequired,
32+
sort: PropTypes.bool,
33+
})
34+
).isRequired,
35+
body: PropTypes.arrayOf(
36+
PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.node]))
37+
).isRequired,
38+
};
39+
40+
export default TableBody;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import PropTypes from 'prop-types';
2+
import { css } from '@emotion/react';
3+
4+
import { Button, Icon } from '@newrelic/gatsby-theme-newrelic';
5+
6+
const TableHeader = ({
7+
headers,
8+
setSortDirection,
9+
setSortField,
10+
sortDirection,
11+
sortField,
12+
}) => {
13+
return (
14+
<thead
15+
css={css`
16+
margin-bottom: 2rem;
17+
18+
th {
19+
text-align: left;
20+
}
21+
`}
22+
>
23+
<tr>
24+
{headers.map(({ label, contentId, sort = false }) => {
25+
const isActiveSort = sortField === contentId;
26+
return (
27+
<th key={contentId}>
28+
{sort ? (
29+
<Button
30+
variant={Button.VARIANT.PLAIN}
31+
onClick={() => {
32+
if (sortField === contentId) {
33+
setSortDirection(DIRECTION.opposite(sortDirection));
34+
} else {
35+
setSortField(contentId);
36+
setSortDirection(DIRECTION.ASC);
37+
}
38+
}}
39+
css={css`
40+
background: none;
41+
`}
42+
>
43+
<b
44+
css={css`
45+
margin: 0 0.25rem;
46+
`}
47+
>
48+
{label}
49+
</b>
50+
<Icon
51+
name={
52+
isActiveSort && sortDirection === DIRECTION.DESC
53+
? 'fe-arrow-up'
54+
: 'fe-arrow-down'
55+
}
56+
size="1rem"
57+
css={css`
58+
position: relative;
59+
top: 1px;
60+
stroke: ${isActiveSort ? '#00ac69' : 'grey'};
61+
stroke-width: 4px;
62+
`}
63+
/>
64+
</Button>
65+
) : (
66+
<b>{label}</b>
67+
)}
68+
</th>
69+
);
70+
})}
71+
</tr>
72+
</thead>
73+
);
74+
};
75+
76+
TableHeader.propTypes = {
77+
headers: PropTypes.arrayOf(
78+
PropTypes.shape({
79+
label: PropTypes.string.isRequired,
80+
contentId: PropTypes.string.isRequired,
81+
sort: PropTypes.bool,
82+
})
83+
).isRequired,
84+
sortField: PropTypes.string.isRequired,
85+
setSortField: PropTypes.func.isRequired,
86+
};
87+
88+
export const DIRECTION = {
89+
ASC: 'asc',
90+
DESC: 'desc',
91+
};
92+
DIRECTION.opposite = (direction) =>
93+
direction === DIRECTION.ASC ? DIRECTION.DESC : DIRECTION.ASC;
94+
95+
export default TableHeader;

src/components/EolTable/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './EolTable';

0 commit comments

Comments
 (0)