setAssetTitle(e.target.value)}
- required
+ {expanded && (
+
-
-
Attached URLs:
-
-
@@ -401,16 +491,14 @@ ChecklistItem.propTypes = {
name: PropTypes.string.isRequired,
statement: PropTypes.string.isRequired,
answer: PropTypes.bool.isRequired,
- scanResult: PropTypes.objectOf(
- PropTypes.arrayOf(PropTypes.string)
- ),
+ scanResult: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
notes: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
author: PropTypes.string.isRequired,
updated: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
- })
+ }),
),
images: PropTypes.arrayOf(
PropTypes.shape({
@@ -418,7 +506,7 @@ ChecklistItem.propTypes = {
uri: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
- })
+ }),
),
urls: PropTypes.arrayOf(
PropTypes.shape({
@@ -426,14 +514,14 @@ ChecklistItem.propTypes = {
hyperlink: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
- })
+ }),
),
subChecklist: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
statement: PropTypes.string.isRequired,
answer: PropTypes.bool.isRequired,
- })
+ }),
),
}).isRequired,
project: PropTypes.object.isRequired,
diff --git a/app/components/ReproChecklist/ReproChecklist.js b/app/components/ReproChecklist/ReproChecklist.js
index eaab298..3cb40ea 100644
--- a/app/components/ReproChecklist/ReproChecklist.js
+++ b/app/components/ReproChecklist/ReproChecklist.js
@@ -3,36 +3,68 @@ import PropTypes from 'prop-types';
import styles from './ReproChecklist.css';
import ChecklistItem from './ChecklistItem/ChecklistItem';
import Error from '../Error/Error';
-import { Typography, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@mui/material';
+import {
+ Typography,
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+} from '@mui/material';
import { SaveAlt } from '@mui/icons-material';
import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';
import GeneralUtil from '../../utils/general';
import ChecklistUtil from '../../utils/checklist';
+import AssetUtil from '../../utils/asset';
+import Constants from '../../constants/constants';
pdfMake.vfs = pdfFonts.pdfMake.vfs;
const path = require('path');
+// These functions are mapped using the statement type to the corresponding scan function
+const scanFunctions = {
+ Dependency: ChecklistUtil.findAssetLanguagesAndDependencies,
+ Data: ChecklistUtil.findDataFiles,
+ Entrypoint: ChecklistUtil.findEntryPointFiles,
+ Documentation: ChecklistUtil.findDocumentationFiles,
+};
+
function ReproChecklist(props) {
- const { project, checklist, error, onUpdated, onAddedNote, onUpdatedNote, onDeletedNote, onSelectedAsset} = props;
+ const {
+ project,
+ checklist,
+ error,
+ onUpdated,
+ onAddedNote,
+ onUpdatedNote,
+ onDeletedNote,
+ onSelectedAsset,
+ } = props;
const [openExportDialog, setOpenExportDialog] = useState(false);
// this useEffect hook is here to load the scan results for all the checklist statements
useEffect(() => {
if (project && checklist && !error) {
- if(project.assets) {
- // scan result for the first checklist statement
- const scanResult1 = ChecklistUtil.findAssetLanguagesAndDependencies(project.assets);
- checklist[0].scanResult = scanResult1;
+ if (project.assets) {
+ // scan the project assets for each checklist statement
+ Constants.CHECKLIST.forEach((statement, index) => {
+ // if used here as there might be a statement that doesn't have a corresponding scan function
+ if (scanFunctions[statement[0]]) {
+ const scanResult = scanFunctions[statement[0]](project.assets);
+ checklist[index].scanResult = scanResult;
+ }
+ });
}
}
}, [project]);
// Handles the update of checklist for changes in the checklist items
const handleItemUpdate = (updatedItem) => {
- const updatedChecklist = checklist.map(item =>
- item.id === updatedItem.id ? updatedItem : item
+ const updatedChecklist = checklist.map((item) =>
+ item.id === updatedItem.id ? updatedItem : item,
);
onUpdated(project, updatedChecklist);
};
@@ -41,7 +73,9 @@ function ReproChecklist(props) {
const handleReportGeneration = (exportNotes) => {
// pdfMake requires base64 encoded images
const checkedIcon = GeneralUtil.convertImageToBase64(path.join(__dirname, 'images/yes.png'));
- const statWrapLogo = GeneralUtil.convertImageToBase64(path.join(__dirname, 'images/banner.png'));
+ const statWrapLogo = GeneralUtil.convertImageToBase64(
+ path.join(__dirname, 'images/banner.png'),
+ );
const documentDefinition = {
content: [
@@ -96,151 +130,157 @@ function ReproChecklist(props) {
},
],
},
- ...checklist.map((item, index) => {
- const maxWidth = 450;
- let subChecklist = [];
- if (item.subChecklist && item.subChecklist.length > 0) {
- subChecklist = item.subChecklist.map((subItem, subIndex) => ({
- columns: [
- {
- text: `${index + 1}.${subIndex + 1} ${subItem.statement}`,
- margin: [15, 5],
- width: '*',
- alignment: 'left',
- },
- subItem.answer ? {
- image: checkedIcon,
- width: 15,
- alignment: 'right',
- margin: [0, 5, 25, 0],
- } : {
- image: checkedIcon,
- width: 15,
- alignment: 'right',
- margin: [0, 5, 1, 0],
- },
- ],
- columnGap: 0,
- }));
- }
+ ...checklist
+ .map((item, index) => {
+ const maxWidth = 450;
+ let subChecklist = [];
+ if (item.subChecklist && item.subChecklist.length > 0) {
+ subChecklist = item.subChecklist.map((subItem, subIndex) => ({
+ columns: [
+ {
+ text: `${index + 1}.${subIndex + 1} ${subItem.statement}`,
+ margin: [15, 5],
+ width: '*',
+ alignment: 'left',
+ },
+ subItem.answer
+ ? {
+ image: checkedIcon,
+ width: 15,
+ alignment: 'right',
+ margin: [0, 5, 25, 0],
+ }
+ : {
+ image: checkedIcon,
+ width: 15,
+ alignment: 'right',
+ margin: [0, 5, 1, 0],
+ },
+ ],
+ columnGap: 0,
+ }));
+ }
- let notes = [];
- if (exportNotes && item.notes && item.notes.length > 0) {
- notes = item.notes.map((note, noteIndex) => ({
- text: `${noteIndex + 1}. ${note.content}`,
- margin: [20, 2],
- width: maxWidth,
- }));
- }
+ let notes = [];
+ if (exportNotes && item.notes && item.notes.length > 0) {
+ notes = item.notes.map((note, noteIndex) => ({
+ text: `${noteIndex + 1}. ${note.content}`,
+ margin: [20, 2],
+ width: maxWidth,
+ }));
+ }
- let images = [];
- const imageWidth = 135;
- if(item.images && item.images.length > 0){
- images = item.images.map((image) => {
- const base64Image = GeneralUtil.convertImageToBase64(image.uri);
- if (base64Image) {
- return {
- image: base64Image,
- width: imageWidth,
- margin: [0, 5],
- alignment: 'center',
- };
- }
- return { text: `Failed to load image: ${image.uri}`, color: 'red' };
- });
- }
+ let images = [];
+ const imageWidth = 135;
+ if (item.images && item.images.length > 0) {
+ images = item.images.map((image) => {
+ const base64Image = GeneralUtil.convertImageToBase64(image.uri);
+ if (base64Image) {
+ return {
+ image: base64Image,
+ width: imageWidth,
+ margin: [0, 5],
+ alignment: 'center',
+ };
+ }
+ return { text: `Failed to load image: ${image.uri}`, color: 'red' };
+ });
+ }
- // Rendering images by rows, as rendering in columns overflows the page and we can't wrap columns under each other,
- // math for 3 images per row is as follows:
- // imageWidth*n + calumnGap*(n-1) <= maxWidth - leftMargin - rightMargin
- // 135*n + 10*(n-1) <= 450 - 20 - 0;
- // n <= 440/145 --> n = 3
- const imagesPerRow = Math.floor((maxWidth - 20 + 10) / (imageWidth + 10));
+ // Rendering images by rows, as rendering in columns overflows the page and we can't wrap columns under each other,
+ // math for 3 images per row is as follows:
+ // imageWidth*n + calumnGap*(n-1) <= maxWidth - leftMargin - rightMargin
+ // 135*n + 10*(n-1) <= 450 - 20 - 0;
+ // n <= 440/145 --> n = 3
+ const imagesPerRow = Math.floor((maxWidth - 20 + 10) / (imageWidth + 10));
- let imageRows = [];
- for (let i = 0; i < images.length; i += imagesPerRow) {
- imageRows.push({
- columns: images.slice(i, i + imagesPerRow),
- columnGap: 10,
- margin: [20, 5],
- });
- }
+ let imageRows = [];
+ for (let i = 0; i < images.length; i += imagesPerRow) {
+ imageRows.push({
+ columns: images.slice(i, i + imagesPerRow),
+ columnGap: 10,
+ margin: [20, 5],
+ });
+ }
+
+ let urls = [];
+ if (item.urls && item.urls.length > 0) {
+ urls = item.urls.map((url, urlIndex) => {
+ return {
+ unbreakable: true,
+ columns: [
+ {
+ text: `${urlIndex + 1}. `,
+ width: 25,
+ margin: [20, 1, 0, 0],
+ alignment: 'left',
+ noWrap: true,
+ },
+ {
+ stack: [
+ {
+ text: url.title,
+ margin: [7, 1],
+ alignment: 'left',
+ style: 'hyperlink',
+ link: url.hyperlink,
+ },
+ {
+ text: url.description,
+ margin: [7, 3],
+ alignment: 'left',
+ },
+ ],
+ width: maxWidth,
+ },
+ ],
+ };
+ });
+ }
- let urls = [];
- if(item.urls && item.urls.length > 0){
- urls = item.urls.map((url, urlIndex) => {
- return {
- unbreakable: true,
+ return [
+ {
columns: [
{
- text: `${urlIndex + 1}. `,
- width: 25,
- margin: [20, 1 , 0, 0],
+ text: `${index + 1}. `,
+ width: 10,
+ margin: [0, 10],
alignment: 'left',
- noWrap: true,
},
{
- stack: [
- {
- text: url.title,
- margin: [7, 1],
- alignment: 'left',
- style: 'hyperlink',
- link: url.hyperlink,
- },
- {
- text: url.description,
- margin: [7, 3],
- alignment: 'left',
- },
- ],
+ text: `${item.statement}`,
+ margin: [0, 10, 25, 0],
width: maxWidth,
+ alignment: 'left',
+ bold: true,
},
+ item.answer
+ ? {
+ image: checkedIcon,
+ width: 15,
+ alignment: 'right',
+ marginRight: 10,
+ marginTop: 12,
+ }
+ : {
+ image: checkedIcon,
+ width: 15,
+ marginLeft: 28,
+ marginTop: 12,
+ },
],
- };
- });
- }
-
- return [
- {
- columns: [
- {
- text: `${index + 1}. `,
- width: 10,
- margin: [0, 10],
- alignment: 'left',
- },
- {
- text: `${item.statement}`,
- margin: [0, 10, 25, 0],
- width: maxWidth,
- alignment: 'left',
- bold: true,
- },
- item.answer ? {
- image: checkedIcon,
- width: 15,
- alignment: 'right',
- marginRight: 10,
- marginTop: 12,
- } : {
- image: checkedIcon,
- width: 15,
- marginLeft: 28,
- marginTop: 12,
- },
- ],
- columnGap: 5,
- },
- ...subChecklist,
- notes.length > 0 ? { text: 'Notes:', margin: [15, 5] } : '',
- ...notes,
- images.length > 0 ? { text: 'Related Images:', margin: [15, 10] } : '',
- ...imageRows,
- urls.length > 0 ? { text: 'Related URLs:', margin: [15, 10] } : '',
- ...urls,
- ];
- }).flat(),
+ columnGap: 5,
+ },
+ ...subChecklist,
+ notes.length > 0 ? { text: 'Notes:', margin: [15, 5] } : '',
+ ...notes,
+ images.length > 0 ? { text: 'Related Images:', margin: [15, 10] } : '',
+ ...imageRows,
+ urls.length > 0 ? { text: 'Related URLs:', margin: [15, 10] } : '',
+ ...urls,
+ ];
+ })
+ .flat(),
],
styles: {
mainHeader: { fontSize: 22, bold: true, color: '#663399' },
@@ -267,53 +307,53 @@ function ReproChecklist(props) {
let content =
Checklist not configured.
;
if (checklist && checklist.length > 0) {
- content =
-
-
Reproducibility Checklist
-
- {checklist.map(item => (
-
- ))}
-
-
-
setOpenExportDialog(true)}
- className={styles.downloadButton}
- >
-
- Report
-
-
-
-
+ content = (
+
+
+ Reproducibility Checklist
+
+
+ {checklist.map((item) => (
+
+ ))}
+
+
+
setOpenExportDialog(true)} className={styles.downloadButton}>
+
+ Report
+
+
+
+
-
-
+
+
+ );
} else if (error) {
content =
There was an error loading the project checklist: {error};
}
diff --git a/app/constants/constants.js b/app/constants/constants.js
index fb4e9f0..76737af 100644
--- a/app/constants/constants.js
+++ b/app/constants/constants.js
@@ -72,7 +72,7 @@ module.exports = {
PROJECT: 'project',
PERSON: 'person',
ASSET: 'asset',
- EXTERNAL_ASSET: 'external asset', // Slightly different from 'asset' in that it lives outside the project folder
+ EXTERNAL_ASSET: 'external asset', // Slightly different from 'asset' in that it lives outside the project folder
CHECKLIST: 'checklist',
},
diff --git a/app/utils/asset.js b/app/utils/asset.js
index 8b486b1..4f2a741 100644
--- a/app/utils/asset.js
+++ b/app/utils/asset.js
@@ -140,6 +140,30 @@ export default class AssetUtil {
return descendantsList;
}
+ /**
+ * Finds all the assets marked as entry points in the asset tree
+ * @param {object} asset The asset to search for entry points
+ * @param {array} entryPointList The list of entry points found so far
+ * @returns {array} The list of entry points found
+ */
+ static findEntryPointAssets(asset, entryPointList = []) {
+ if (!asset) {
+ return entryPointList;
+ }
+
+ if (asset.attributes && asset.attributes.entrypoint) {
+ entryPointList.push(asset);
+ }
+
+ if (asset.children) {
+ asset.children.forEach((child) => {
+ AssetUtil.findEntryPointAssets(child, entryPointList);
+ });
+ }
+
+ return entryPointList;
+ }
+
/**
* Recursively collect and flatten all asset notes into an array
* @param {object} asset The asset to find all notes for
diff --git a/app/utils/checklist.js b/app/utils/checklist.js
index 9cf38dc..759cba1 100644
--- a/app/utils/checklist.js
+++ b/app/utils/checklist.js
@@ -1,5 +1,7 @@
import Constants from '../constants/constants';
import AssetsConfig from '../constants/assets-config';
+import AssetUtil from './asset';
+import WorkflowUtil from './workflow';
const path = require('path');
export default class ChecklistUtil {
@@ -26,43 +28,147 @@ export default class ChecklistUtil {
}
/**
- * This function returns the languages and dependencies of an asset and its children
+ * This function returns the languages and dependencies of the asset
* @param {object} asset The asset to find the languages and dependencies of
- * @param {object} languages Empty object that acts like a map to store the languages found as keys
- * @param {object} dependencies Empty object that acts like a map to store the dependencies found as keys
* @returns {object} An object containing the languages and dependencies found as arrays
*/
- static findAssetLanguagesAndDependencies(asset, languages = {}, dependencies = {}) {
+ static findAssetLanguagesAndDependencies(asset) {
if (!asset) {
return {
languages: [],
- dependencies: []
+ dependencies: [],
};
}
- if (asset.type === Constants.AssetType.FILE && asset.contentTypes.includes(Constants.AssetContentType.CODE) ) {
+
+ return {
+ languages: ChecklistUtil.findAssetLanguages(asset),
+ dependencies: ChecklistUtil.findAssetDependencies(asset),
+ };
+ }
+
+ /**
+ * This function returns the languages of an asset and its children recursively
+ * @param {object} asset The asset to find the languages of
+ * @param {object} languages Empty object that acts like a map to store the languages found as keys
+ * @returns {array} An array containing the languages found
+ */
+ static findAssetLanguages(asset, languages = {}) {
+ if (
+ asset.type === Constants.AssetType.FILE &&
+ asset.contentTypes.includes(Constants.AssetContentType.CODE)
+ ) {
const lastSep = asset.uri.lastIndexOf(path.sep);
const fileName = asset.uri.substring(lastSep + 1);
const ext = fileName.split('.').pop();
- if(ext){
+ if (ext) {
AssetsConfig.contentTypes.forEach((contentType) => {
// Ensures both the extension and content type are for code files
- if(contentType.categories.includes(Constants.AssetContentType.CODE) && contentType.extensions.includes(ext)) {
+ if (
+ contentType.categories.includes(Constants.AssetContentType.CODE) &&
+ contentType.extensions.includes(ext)
+ ) {
languages[contentType.name] = true;
}
});
}
}
- if(asset.children){
- asset.children.forEach(child => {
- this.findAssetLanguagesAndDependencies(child, languages, dependencies);
+ if (asset.children) {
+ asset.children.forEach((child) => {
+ ChecklistUtil.findAssetLanguages(child, languages);
});
}
- return {
- languages: Object.keys(languages),
- dependencies: Object.keys(dependencies)
- };
+ return Object.keys(languages);
+ }
+
+ /**
+ * This function returns the dependencies of an asset and its children recursively
+ * @param {object} asset The asset to find the dependencies of
+ * @param {object} dependencies Empty object that acts like a map to store the dependencies found as keys
+ * @returns {array} An array containing the dependencies found
+ */
+ static findAssetDependencies(asset) {
+ const dependencies = [];
+ const assetDepedencies = WorkflowUtil.getAllLibraryDependencies(asset);
+ assetDepedencies.forEach((x) => {
+ if (x.assetType && x.assetType !== Constants.AssetType.GENERIC) {
+ x.dependencies.forEach((dep) => {
+ if (dependencies.findIndex((i) => i === dep.id) === -1) {
+ dependencies.push(WorkflowUtil.getShortDependencyName(dep.id));
+ }
+ });
+ }
+ });
+ return dependencies;
+ }
+
+ /** This function finds the data files in the asset and its children recursively
+ * @param {object} asset The asset to find the data files within
+ * @param {array} dataFiles An array to store the data files found
+ * @returns {object} An object containing the data files found
+ */
+ static findDataFiles(asset, dataFiles = []) {
+ if (!asset) {
+ return { dataFiles: dataFiles };
+ }
+ if (
+ asset.type === Constants.AssetType.FILE &&
+ asset.contentTypes.includes(Constants.AssetContentType.DATA)
+ ) {
+ const fileName = AssetUtil.getAssetNameFromUri(asset.uri);
+ dataFiles.push(fileName);
+ }
+
+ if (asset.children) {
+ asset.children.forEach((child) => {
+ ChecklistUtil.findDataFiles(child, dataFiles);
+ });
+ }
+
+ return { dataFiles: dataFiles };
+ }
+
+ /**
+ * This function gets the entry point file names from the entryPoints assets array
+ * @param {object} asset The asset to find the entry point files within
+ * @returns {object} An object containing the entry point file names found
+ */
+ static findEntryPointFiles(asset) {
+ const entryPoints = AssetUtil.findEntryPointAssets(asset);
+ const entryPointFiles = [];
+ entryPoints?.forEach((entryPoint) => {
+ const fileName = AssetUtil.getAssetNameFromUri(entryPoint.uri);
+ entryPointFiles.push(fileName);
+ });
+ return { entryPoints: entryPointFiles };
+ }
+
+ /**
+ * This function finds the documentation files in the asset
+ * @param {object} asset The asset to find the documentation files within
+ * @param {array} documentationFiles An array to store the documentation files found
+ * @returns {object} An object containing the documentation files found
+ */
+ static findDocumentationFiles(asset, documentationFiles = []) {
+ if (!asset) {
+ return { documentationFiles: documentationFiles };
+ }
+ if (
+ asset.type === Constants.AssetType.FILE &&
+ asset.contentTypes.includes(Constants.AssetContentType.DOCUMENTATION)
+ ) {
+ const fileName = AssetUtil.getAssetNameFromUri(asset.uri);
+ documentationFiles.push(fileName);
+ }
+
+ if (asset.children) {
+ asset.children.forEach((child) => {
+ ChecklistUtil.findDocumentationFiles(child, documentationFiles);
+ });
+ }
+
+ return { documentationFiles: documentationFiles };
}
}
diff --git a/app/utils/workflow.js b/app/utils/workflow.js
index aff630b..f24bff0 100644
--- a/app/utils/workflow.js
+++ b/app/utils/workflow.js
@@ -45,10 +45,8 @@ export default class WorkflowUtil {
// approximating it. So we roughly cut the max length in half, but it may not be exact, and
// we don't count the 3 periods towards the max length (meaning, our resulting label will
// technically go over the max length we detect).
- // eslint-disable-next-line prettier/prettier
- const beginningPart = name.substring(0, (Constants.MAX_GRAPH_LABEL_LENGTH / 2));
- // eslint-disable-next-line prettier/prettier
- const endPart = name.substring(name.length - (Constants.MAX_GRAPH_LABEL_LENGTH / 2) + 1);
+ const beginningPart = name.substring(0, Constants.MAX_GRAPH_LABEL_LENGTH / 2);
+ const endPart = name.substring(name.length - Constants.MAX_GRAPH_LABEL_LENGTH / 2 + 1);
return `${beginningPart}...${endPart}`;
}
@@ -310,4 +308,47 @@ export default class WorkflowUtil {
}
return dependencies.flat();
}
+
+ /**
+ * Given an asset, get the list of library dependencies
+ * @param {object} asset The asset to find library dependencies for
+ * @returns Array of library dependencies
+ */
+ static getLibraryDependencies(asset) {
+ if (!asset) {
+ return [];
+ }
+
+ const libraries = [];
+ WorkflowUtil._getMetadataDependencies(asset, PythonHandler.id, libraries, [], []);
+ WorkflowUtil._getMetadataDependencies(asset, RHandler.id, libraries, [], []);
+ WorkflowUtil._getMetadataDependencies(asset, SASHandler.id, libraries, [], []);
+ WorkflowUtil._getMetadataDependencies(asset, StataHandler.id, libraries, [], []);
+
+ return libraries;
+ }
+
+ /**
+ * This function finds all library dependencies of an asset and its children recursively, that can be used to find overall project dependencies with `project.assets` as input
+ * @param {object} asset The asset to find the library dependencies of
+ * @returns {array} An array containing the library dependencies found
+ */
+ static getAllLibraryDependencies(asset) {
+ const libraries = asset
+ ? [
+ {
+ asset: asset.uri,
+ assetType: WorkflowUtil.getAssetType(asset),
+ dependencies: WorkflowUtil.getLibraryDependencies(asset),
+ },
+ ]
+ : [];
+ if (!asset || !asset.children) {
+ return libraries.flat();
+ }
+ for (let index = 0; index < asset.children.length; index++) {
+ libraries.push(WorkflowUtil.getAllLibraryDependencies(asset.children[index]));
+ }
+ return libraries.flat();
+ }
}
diff --git a/test/utils/checklist.spec.js b/test/utils/checklist.spec.js
index c173033..93439f6 100644
--- a/test/utils/checklist.spec.js
+++ b/test/utils/checklist.spec.js
@@ -1,5 +1,5 @@
-import ChecklistUtil from "../../app/utils/checklist";
-import Constants from "../../app/constants/constants";
+import ChecklistUtil from '../../app/utils/checklist';
+import Constants from '../../app/constants/constants';
describe('utils', () => {
describe('ChecklistUtil', () => {
@@ -16,14 +16,22 @@ describe('utils', () => {
});
it('should return correct languages and dependencies for valid code assets', () => {
- const languages = {'R': ['r', 'rmd', 'rnw', 'snw'], 'Python': ['py', 'py3', 'pyi'], 'SAS': ['sas'], 'Stata': ['do', 'ado', 'mata'], 'HTML': ['htm', 'html']};
+ const languages = {
+ R: ['r', 'rmd', 'rnw', 'snw'],
+ Python: ['py', 'py3', 'pyi'],
+ SAS: ['sas'],
+ Stata: ['do', 'ado', 'mata'],
+ HTML: ['htm', 'html'],
+ };
Object.keys(languages).forEach((lang) => {
languages[lang].forEach((ext) => {
- expect(ChecklistUtil.findAssetLanguagesAndDependencies({
- type: Constants.AssetType.FILE,
- contentTypes: [Constants.AssetContentType.CODE],
- uri: `path/to/file.${ext}`,
- })).toEqual({
+ expect(
+ ChecklistUtil.findAssetLanguagesAndDependencies({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: `path/to/file.${ext}`,
+ }),
+ ).toEqual({
languages: [lang],
dependencies: [],
});
@@ -32,102 +40,255 @@ describe('utils', () => {
});
it('should return empty result for non-code assets (data, documentation)', () => {
- expect(ChecklistUtil.findAssetLanguagesAndDependencies({
- type: Constants.AssetType.FILE,
- contentTypes: [Constants.AssetContentType.DATA],
- uri: 'path/to/file.csv',
- })).toEqual({
+ expect(
+ ChecklistUtil.findAssetLanguagesAndDependencies({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.DATA],
+ uri: 'path/to/file.csv',
+ }),
+ ).toEqual({
languages: [],
dependencies: [],
});
});
- it('should return empty result for unmatching content type and asset type', () => {
- expect(ChecklistUtil.findAssetLanguagesAndDependencies({
- type: Constants.AssetType.FILE,
- contentTypes: [Constants.AssetContentType.DATA],
- uri: 'path/to/file.py',
- })).toEqual({
+ it('should return empty result for unmatching content type and extension', () => {
+ expect(
+ ChecklistUtil.findAssetLanguagesAndDependencies({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.DATA],
+ uri: 'path/to/file.py',
+ }),
+ ).toEqual({
languages: [],
dependencies: [],
});
- expect(ChecklistUtil.findAssetLanguagesAndDependencies({
- type: Constants.AssetType.FILE,
- contentTypes: [Constants.AssetContentType.CODE],
- uri: 'path/to/file.csv',
- })).toEqual({
+ expect(
+ ChecklistUtil.findAssetLanguagesAndDependencies({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/file.csv',
+ }),
+ ).toEqual({
languages: [],
dependencies: [],
});
});
it('should return empty result for directory/folder type assets', () => {
- expect(ChecklistUtil.findAssetLanguagesAndDependencies({
- type: Constants.AssetType.DIRECTORY,
- contentTypes: [Constants.AssetContentType.CODE],
- uri: 'path/to/directory/',
- })).toEqual({
+ expect(
+ ChecklistUtil.findAssetLanguagesAndDependencies({
+ type: Constants.AssetType.DIRECTORY,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/directory/',
+ }),
+ ).toEqual({
languages: [],
dependencies: [],
});
});
it('should not identify malformed URIs', () => {
- expect(ChecklistUtil.findAssetLanguagesAndDependencies({
- type: Constants.AssetType.FILE,
- contentTypes: [Constants.AssetContentType.CODE],
- uri: 'path/to/malformed-uri.',
- })).toEqual({
+ expect(
+ ChecklistUtil.findAssetLanguagesAndDependencies({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/malformed-uri.',
+ }),
+ ).toEqual({
languages: [],
dependencies: [],
});
});
it('should ignore random/unknown extensions that are not in the content types', () => {
- expect(ChecklistUtil.findAssetLanguagesAndDependencies({
- type: Constants.AssetType.FILE,
- contentTypes: [Constants.AssetContentType.CODE],
- uri: 'path/to/file.cmd',
- })).toEqual({
+ expect(
+ ChecklistUtil.findAssetLanguagesAndDependencies({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/file.cmd',
+ }),
+ ).toEqual({
languages: [],
dependencies: [],
});
});
it('should handle nested assets and recurse properly', () => {
- expect(ChecklistUtil.findAssetLanguagesAndDependencies({
- type: Constants.AssetType.FOLDER,
- contentTypes: [Constants.AssetContentType.CODE],
- uri: 'path/to/folder',
- children: [
- {
- type: Constants.AssetType.FILE,
- contentTypes: [Constants.AssetContentType.CODE],
- uri: 'path/to/folder/file1.py',
- },
- {
- type: Constants.AssetType.FILE,
- contentTypes: [Constants.AssetContentType.CODE],
- uri: 'path/to/folder/file2.r',
- }
- ],
- })).toEqual({
+ expect(
+ ChecklistUtil.findAssetLanguagesAndDependencies({
+ type: Constants.AssetType.FOLDER,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/folder',
+ children: [
+ {
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/folder/file1.py',
+ },
+ {
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/folder/file2.r',
+ },
+ ],
+ }),
+ ).toEqual({
languages: ['Python', 'R'], // don't change the languages ordering in this array
dependencies: [],
});
});
it('should not crash when asset has no extension in its URI', () => {
- expect(ChecklistUtil.findAssetLanguagesAndDependencies({
- type: Constants.AssetType.FILE,
- contentTypes: [Constants.AssetContentType.CODE],
- uri: 'path/to/file',
- })).toEqual({
+ expect(
+ ChecklistUtil.findAssetLanguagesAndDependencies({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/file',
+ }),
+ ).toEqual({
languages: [],
dependencies: [],
});
});
});
+
+ describe('findDataFiles', () => {
+ it('should return empty result when asset is null or undefined', () => {
+ expect(ChecklistUtil.findDataFiles(null)).toEqual({ dataFiles: [] });
+ expect(ChecklistUtil.findDataFiles(undefined)).toEqual({ dataFiles: [] });
+ });
+
+ it('should return empty result when asset is not a data file', () => {
+ expect(
+ ChecklistUtil.findDataFiles({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/file.py',
+ }),
+ ).toEqual({ dataFiles: [] });
+ });
+
+ it('should return data file name when asset is a data file', () => {
+ expect(
+ ChecklistUtil.findDataFiles({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.DATA],
+ uri: 'path/to/file.csv',
+ }),
+ ).toEqual({ dataFiles: ['file.csv'] });
+ });
+
+ it('should return data file names for nested data files', () => {
+ expect(
+ ChecklistUtil.findDataFiles({
+ type: Constants.AssetType.FOLDER,
+ contentTypes: [Constants.AssetContentType.DATA],
+ uri: 'path/to/folder',
+ children: [
+ {
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.DATA],
+ uri: 'path/to/folder/file1.csv',
+ },
+ {
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.DATA],
+ uri: 'path/to/folder/file2.csv',
+ },
+ ],
+ }),
+ ).toEqual({ dataFiles: ['file1.csv', 'file2.csv'] });
+ });
+ });
+
+ describe('findEntryPointFiles', () => {
+ it('should return empty result when entryPoints is null or undefined', () => {
+ expect(ChecklistUtil.findEntryPointFiles(null)).toEqual({ entryPoints: [] });
+ expect(ChecklistUtil.findEntryPointFiles(undefined)).toEqual({ entryPoints: [] });
+ });
+
+ it('should return entry point file names for given entrypoint assets', () => {
+ expect(
+ ChecklistUtil.findEntryPointFiles({
+ type: Constants.AssetType.FOLDER,
+ uri: 'path/to/folder',
+ children: [
+ {
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/folder/file1.py',
+ attributes: {
+ entrypoint: true,
+ },
+ },
+ {
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/folder/file2.py',
+ attributes: {
+ entrypoint: false,
+ },
+ },
+ ],
+ }),
+ ).toEqual({ entryPoints: ['file1.py'] });
+ });
+ });
+
+ describe('findDocumentationFiles', () => {
+ it('should return empty result when asset is null or undefined', () => {
+ expect(ChecklistUtil.findDocumentationFiles(null)).toEqual({ documentationFiles: [] });
+ expect(ChecklistUtil.findDocumentationFiles(undefined)).toEqual({ documentationFiles: [] });
+ });
+
+ it('should return empty result when asset is not a documentation file', () => {
+ expect(
+ ChecklistUtil.findDocumentationFiles({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/file.py',
+ }),
+ ).toEqual({ documentationFiles: [] });
+ });
+
+ it('should return documentation file name when asset is a documentation file', () => {
+ expect(
+ ChecklistUtil.findDocumentationFiles({
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.DOCUMENTATION],
+ uri: 'path/to/file.md',
+ }),
+ ).toEqual({ documentationFiles: ['file.md'] });
+ });
+
+ it('should return documentation file names for nested documentation files', () => {
+ expect(
+ ChecklistUtil.findDocumentationFiles({
+ type: Constants.AssetType.FOLDER,
+ contentTypes: [Constants.AssetContentType.DOCUMENTATION],
+ uri: 'path/to/folder',
+ children: [
+ {
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.DOCUMENTATION],
+ uri: 'path/to/folder/file1.md',
+ },
+ {
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.DOCUMENTATION],
+ uri: 'path/to/folder/file2.md',
+ },
+ {
+ type: Constants.AssetType.FILE,
+ contentTypes: [Constants.AssetContentType.CODE],
+ uri: 'path/to/folder/file3.py',
+ },
+ ],
+ }),
+ ).toEqual({ documentationFiles: ['file1.md', 'file2.md'] });
+ });
+ });
});
});