Skip to content

Commit

Permalink
docs: refactoring Usage Insights (#2267)
Browse files Browse the repository at this point in the history
* refactor: refactoring usage insights
* refactor: refactorimg Usage Insights in www
* refactor: refactoring structure
* refactor: refactoring gatsby node
* refactor: added paragon type of usage
  • Loading branch information
PKulkoRaccoonGang authored Jun 16, 2023
1 parent f7cb706 commit 2b8a1f3
Show file tree
Hide file tree
Showing 23 changed files with 690 additions and 570 deletions.
191 changes: 7 additions & 184 deletions www/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,193 +4,16 @@
* See: https://www.gatsbyjs.com/docs/node-apis/
*/

// You can delete this file if you're not using it
const path = require('path');
const { createFilePath } = require('gatsby-source-filesystem');
const sass = require('sass');
const css = require('css');
const createPages = require('./utils/createPages');
const onCreateNode = require('./utils/onCreateNode');
const onCreateWebpackConfig = require('./utils/onCreateWebpackConfig');
const createCssUtilityClassNodes = require('./utils/createCssUtilityClassNodes');

const fs = require('fs');
const { INSIGHTS_PAGES } = require('./src/config');
const { getThemesSCSSVariables, processComponentSCSSVariables } = require('./theme-utils');
exports.onCreateWebpackConfig = ({ actions }) => onCreateWebpackConfig(actions);

exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
resolve: {
alias: {
'~paragon-react': path.resolve(__dirname, '../src'),
'~paragon-style': path.resolve(__dirname, '../scss'),
'~paragon-icons': path.resolve(__dirname, '../icons'),
},
},
});
};

exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
// you only want to operate on `Mdx` nodes. If you had content from a
// remote CMS you could also check to see if the parent node was a
// `File` node here
if (node.internal.type === 'Mdx') {
const value = createFilePath({ node, getNode })
.split('README')[0]
.toLowerCase();

const isChangelogNode = node.fileAbsolutePath && node.fileAbsolutePath.endsWith('CHANGELOG.md');

createNodeField({
// Name of the field you are adding
name: 'slug',
// Individual MDX node
node,
// Generated value based on filepath with 'components' prefix. you
// don't need a separating '/' before the value because
// createFilePath returns a path with the leading '/'.
value: isChangelogNode ? 'changelog' : `/components${value}`,
});
}
};

exports.createPages = async ({ graphql, actions, reporter }) => {
// Destructure the createPage function from the actions object
const { createPage, createRedirect } = actions;
// MDX transforms markdown generated by gatsby-transformer-react-docgen
// This query filters out all of those markdown nodes and assumes all others
// are for page creation purposes.
const result = await graphql(`
query {
allMdx(
filter: {
parent: {
internal: { owner: { nin: "gatsby-transformer-react-docgen" } }
}
}
) {
edges {
node {
id
fields {
slug
}
frontmatter {
components
}
slug
}
}
}
}
`);
if (result.errors) {
reporter.panicOnBuild('🚨 ERROR: Loading createPages query');
}
// Create component detail pages.
const components = result.data.allMdx.edges;

const themesSCSSVariables = await getThemesSCSSVariables();

// you'll call `createPage` for each result
// eslint-disable-next-line no-restricted-syntax
for (const { node } of components) {
const componentDir = node.slug.split('/')[0];
const variablesPath = path.resolve(__dirname, `../src/${componentDir}/_variables.scss`);
let scssVariablesData = {};

if (fs.existsSync(variablesPath)) {
// eslint-disable-next-line no-await-in-loop
scssVariablesData = await processComponentSCSSVariables(variablesPath, themesSCSSVariables);
}

createPage({
// This is the slug you created before
// (or `node.frontmatter.slug`)
path: node.fields.slug,
// This component will wrap our MDX content
component: path.resolve('./src/templates/component-page-template.tsx'),
// You can use the values in this context in
// our page layout component
context: { id: node.id, components: node.frontmatter.components || [], scssVariablesData },
});
}

INSIGHTS_PAGES.forEach(({ path: pagePath, tab }) => {
createPage({
path: pagePath,
component: require.resolve('./src/pages/insights.tsx'),
context: { tab },
});
});

createRedirect({
fromPath: '/playroom',
toPath: '/playroom/index.html',
});

createRedirect({
fromPath: '/playroom/preview',
toPath: '/playroom/preview/index.html',
});
};

function createCssUtilityClassNodes({
actions,
createNodeId,
createContentDigest,
}) {
const { createNode } = actions;

// We convert to CSS first since we prefer the real values over tokens.
const compiledCSS = sass
.renderSync({
file: path.resolve(__dirname, '../scss/core/utilities-only.scss'),
// Resolve tildes the way webpack would in our base npm project
importer(url) {
let resolvedUrl = url;
if (url[0] === '~') {
resolvedUrl = path.resolve(__dirname, '../node_modules', url.substr(1));
}
return { file: resolvedUrl };
},
})
.css.toString();

const sheet = css.parse(compiledCSS).stylesheet;

sheet.rules.forEach(({ selectors, position, declarations }) => {
if (!selectors) { return; }

selectors.forEach(selector => {
if (selector[0] !== '.') { return; } // classes only

const classSelector = selector.substr(1);

const nodeData = {
selector: classSelector,
declarations: declarations.map(
({ property, value }) => `${property}: ${value};`,
),
isUtility:
declarations.length === 1
&& declarations[0].value.includes('!important'),
};

const nodeMeta = {
id: createNodeId(
`rule-${classSelector}-${position.start.line}-${position.end.line}`,
),
parent: null,
children: [],
internal: {
type: 'CssUtilityClasses',
contentDigest: createContentDigest(nodeData),
},
};
exports.onCreateNode = ({ node, actions, getNode }) => onCreateNode(node, actions, getNode);

const node = { ...nodeData, ...nodeMeta };
createNode(node);
});
});
}
exports.createPages = ({ graphql, actions, reporter }) => createPages(graphql, actions, reporter);

exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) => {
createCssUtilityClassNodes({ actions, createNodeId, createContentDigest });
Expand Down
58 changes: 58 additions & 0 deletions www/src/components/insights/ComponentUsage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DataTable } from '~paragon-react';
import ComponentUsageExamples, { IComponentUsageExamples } from './ComponentUsageExamples';

import { IComponentUsage } from '../../types/types';

function ComponentUsage({ name, componentUsageInProjects }: IComponentUsage) {
return (
<div className="mb-5">
<h3 className="mb-4">{name}</h3>
<DataTable
isExpandable
isSortable
itemCount={componentUsageInProjects.length} // eslint-disable-line
data={componentUsageInProjects}
renderRowSubComponent={({ row }: IComponentUsageExamples) => (
<ComponentUsageExamples row={row} componentName={name} />
)}
columns={[
{
id: 'expander',
Header: DataTable.ExpandAll,
Cell: DataTable.ExpandRow,
},
{
Header: 'Project Name',
accessor: 'folderName',
},
{ Header: 'Paragon Version', accessor: 'version' },
{ Header: 'Instance Count', accessor: 'componentUsageCount' },
]}
>
<DataTable.Table />
<DataTable.EmptyTable content="No usages" />
</DataTable>
</div>
);
}

ComponentUsage.propTypes = {
name: PropTypes.string.isRequired,
componentUsageInProjects: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
folderName: PropTypes.string,
version: PropTypes.string,
repositoryUrl: PropTypes.string,
componentUsageCount: PropTypes.number,
usages: PropTypes.arrayOf(PropTypes.shape({
column: PropTypes.number,
filePath: PropTypes.string,
line: PropTypes.number,
version: PropTypes.string,
})),
})).isRequired,
};

export default ComponentUsage;
32 changes: 32 additions & 0 deletions www/src/components/insights/ComponentsUsage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import ComponentUsage from './ComponentUsage';

import componentsUsage from '../../utils/componentsUsage';
import getEmptyMessage from '../../utils/getEmptyMessage';
import usagePropTypes from '../../utils/usagePropTypes';
import removeDotsFromKeys from '../../utils/removeDotsFromKey';

function ComponentsUsage({ data }: { data: string[] }) {
const filteredComponentsUsage = removeDotsFromKeys(componentsUsage);

return (
<div className="pt-5 mb-5">
{data.length ? data.sort().map(name => {
if (filteredComponentsUsage[name]) {
return (
<ComponentUsage
key={name}
name={name}
componentUsageInProjects={filteredComponentsUsage[name]}
/>
);
}
return null;
}) : getEmptyMessage('components')}
</div>
);
}

ComponentsUsage.propTypes = usagePropTypes;

export default ComponentsUsage;
24 changes: 24 additions & 0 deletions www/src/components/insights/HooksUsage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import ComponentUsage from './ComponentUsage';

import componentsUsage from '../../utils/componentsUsage';
import getEmptyMessage from '../../utils/getEmptyMessage';
import usagePropTypes from '../../utils/usagePropTypes';

function HooksUsage({ data }: { data: string[] }) {
return (
<div className="pt-5 mb-5">
{data.length ? data.sort().map(name => (
<ComponentUsage
key={name}
name={name}
componentUsageInProjects={componentsUsage[name]}
/>
)) : getEmptyMessage('hooks')}
</div>
);
}

HooksUsage.propTypes = usagePropTypes;

export default HooksUsage;
24 changes: 24 additions & 0 deletions www/src/components/insights/IconsUsage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import ComponentUsage from './ComponentUsage';

import componentsUsage from '../../utils/componentsUsage';
import getEmptyMessage from '../../utils/getEmptyMessage';
import usagePropTypes from '../../utils/usagePropTypes';

function IconsUsage({ data }: { data: string[] }) {
return (
<div className="pt-5 mb-5">
{data.length ? data.sort().map(name => (
<ComponentUsage
key={name}
name={name}
componentUsageInProjects={componentsUsage[name]}
/>
)) : getEmptyMessage('utils')}
</div>
);
}

IconsUsage.propTypes = usagePropTypes;

export default IconsUsage;
42 changes: 42 additions & 0 deletions www/src/components/insights/ProjectsUsage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { DataTable } from '~paragon-react';
import ProjectUsageExamples, { IProjectUsageExamples } from './ProjectUsageExamples';

import getDependentProjectsUsages from '../../utils/getDependentProjectsUsages';

function ProjectsUsage() {
const dependentProjects = getDependentProjectsUsages();

return (
<div className="pt-5 mb-5">
<h3 className="mb-4">Projects in Open edX consuming Paragon</h3>
<DataTable
isExpandable
isSortable
itemCount={dependentProjects.length}
data={dependentProjects}
renderRowSubComponent={({ row }: IProjectUsageExamples) => <ProjectUsageExamples row={row} />}
columns={[
{
id: 'expander',
Header: DataTable.ExpandAll,
Cell: DataTable.ExpandRow,
},
{
Header: 'Project Name',
accessor: 'folderName',
},
{ Header: 'Paragon Version', accessor: 'version' },
{ Header: 'Import Count', accessor: 'count' },
]}
>
<DataTable.TableControlBar />
<DataTable.Table />
<DataTable.EmptyTable content="No projects" />
<DataTable.TableFooter />
</DataTable>
</div>
);
}

export default ProjectsUsage;
Loading

0 comments on commit 2b8a1f3

Please sign in to comment.