From 84170227c1bb6ea61568e7776ee5e10058168154 Mon Sep 17 00:00:00 2001 From: Luke R Date: Tue, 28 May 2024 08:45:11 -0500 Subject: [PATCH 01/43] Added documentation around reproducibility checklist features --- docs/ReproducibilityChecklists-Planning.md | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 docs/ReproducibilityChecklists-Planning.md diff --git a/docs/ReproducibilityChecklists-Planning.md b/docs/ReproducibilityChecklists-Planning.md new file mode 100644 index 0000000..92e7f53 --- /dev/null +++ b/docs/ReproducibilityChecklists-Planning.md @@ -0,0 +1,37 @@ +# Reproducible Research Checklists + +This document provides design guidance and general requirements for the reproducible research checklist feature in StatWrap. + +## Modes of Operation + +There are two modes of operation for the checklist: + +### 1. Automated information gathering / decision making (StatWrap) + +StatWrap will use information from project assets and user-entered metadata in order to provide information about reproducible research practices. This will be used to **inform** users, but will not make a binary indicator (yes/no) about if a practice is "reproducible" or not. + +StatWrap should be configured to provide information about the following categories of data. The way the data could be displayed is noted, but is not intended to limit the final implementation. For each of these, there should be a service method that is able to perform the processed logic. Unlike the actual checklist UI, these items are not configurable by the end user, they are built within StatWrap. + +1. **Summarize the software used** - this should include programming language(s) and dependencies. Programming languages can be inferred from file extensions. Dependencies can be inferred from specific references in code files (already processed by StatWrap), as well as dependency files used for specific languages (e.g., `requirements.txt` for Python). StatWrap does not need to determine the specific version used in any environment if it is not specified elsewhere. +2. **Multiple versions of a file** - use a heuristic from file names to determine if the user has versioned files using a naming convention, as opposed to using source control. For example, `main_analysis.R` and `main_analysis_v2.R` or `main_analysis-jd.R`. StatWrap could use metrics other than the file name, such as similarity of file content, to further infer if the files are likely versioned. For a grouping of files that StatWrap thinks are versioned, it should check attributes to: + 1. See if any of the files are marked as 'archived'. + 2. If more than one file is marked as an 'entry point'. +3. **Location of project documentation** - StatWrap should detect the presence of a README file (any extension), and if there is a folder named `doc`, `docs`, or `documentation`. If so, display these file(s)/folder(s) as the projecct documentation. +4. **Absolute paths used** - StatWrap should detect if explicit absolute paths are used within the code files within a project. This should detect across Mac, Linux, and Windows, including multiple ways of denoting paths across OS (e.g., on Windows languages like R will let you reference `C:/test/file.csv` or `C:\test\file.csv`), as well as fully qualified network paths (e.g., `\\research\files\test.csv`). StatWrap should report which file(s) are using absolute paths. +5. **Binary / proprietary file formats for data** - flag data files that are not purely text-based. This can be done from a configureable list of file extensions. +6. **List categories of data files/folders** - using user-assigned attributes, or folder naming conventions, display the list of files associated with different categories of data: + 1. Input data - data flagged with an attribute of "Input / Raw Data", or data contained in folders using a configurable list of folder names (e.g., `raw`, `input`) + 2. Derived data - data flagged with an attribute of "Derived", or data contained in folders using a configurable list of folder names (e.g., `output`, `derived`) + 3. Data (general) - data files that we cannot otherwise categorize +7. **List categories of code files/folders** - using user-assigned attributes, or folder naming conventions, display the list of code files associated with different processing steps. Note that a single code file may have multiple annotations: + 1. Data acquisition - used to import / pull data. This would be flagged with an attribute of "Data acquisition". + 2. Data processing - this would be flagged with an attribute of "Data processing" + 3. Analysis - this would be flagged with an attribute of "Analysis" +8. **Entry points** - StatWrap should list all code files flagged as entry points by the user. If none, and code files exist, this should also be noted ("No entry point has been specified"). This could be combined with the previous item. +9. **External assets** - identify references in the code to: databases, APIs, web URLs that contain data. Summarize these for the user as external assets that are being referenced. + +### 2. Human-entered information / decision making (user) + +This is more of the traditional "checklist", where the user is presented with a list of questions that they may attest to. Some of the answers may be pre-filled using data collected by StatWrap as defined in the automated information gathering (previous section). + +(See proposal by Adi) \ No newline at end of file From db868760e2a32dc67d84263db55270852a01dc0d Mon Sep 17 00:00:00 2001 From: AdiAkhileshSingh15 Date: Wed, 26 Jun 2024 13:19:50 +0530 Subject: [PATCH 02/43] basic UI skeleton Signed-off-by: AdiAkhileshSingh15 --- app/components/Project/Project.js | 12 ++ .../ReproChecklists/ChecklistItem.css | 87 ++++++++++ .../ReproChecklists/ChecklistItem.js | 163 ++++++++++++++++++ .../ReproChecklists/ReproChecklists.css | 8 + .../ReproChecklists/ReproChecklists.js | 114 ++++++++++++ docs/ReproducibilityChecklists-DataModels.md | 80 +++++++++ 6 files changed, 464 insertions(+) create mode 100644 app/components/ReproChecklists/ChecklistItem.css create mode 100644 app/components/ReproChecklists/ChecklistItem.js create mode 100644 app/components/ReproChecklists/ReproChecklists.css create mode 100644 app/components/ReproChecklists/ReproChecklists.js create mode 100644 docs/ReproducibilityChecklists-DataModels.md diff --git a/app/components/Project/Project.js b/app/components/Project/Project.js index bfe6d42..56c790e 100644 --- a/app/components/Project/Project.js +++ b/app/components/Project/Project.js @@ -13,6 +13,7 @@ import Assets from './Assets/Assets'; import Workflow from '../Workflow/Workflow'; import People from '../People/People'; import ProjectLog from '../ProjectLog/ProjectLog'; +import ReproChecklists from '../ReproChecklists/ReproChecklists'; import ProjectNotes from '../ProjectNotes/ProjectNotes'; import { ActionType, EntityType, DescriptionContentType } from '../../constants/constants'; import AssetUtil from '../../utils/asset'; @@ -681,6 +682,13 @@ class Project extends Component { /> ) : null; + const reproChecklists = + content = (
@@ -709,6 +717,7 @@ class Project extends Component { +
@@ -729,6 +738,9 @@ class Project extends Component { {projectLog} + + {reproChecklists} +
); } diff --git a/app/components/ReproChecklists/ChecklistItem.css b/app/components/ReproChecklists/ChecklistItem.css new file mode 100644 index 0000000..dc06332 --- /dev/null +++ b/app/components/ReproChecklists/ChecklistItem.css @@ -0,0 +1,87 @@ +.item { + padding: 10px; + border-bottom: 1px solid #e5e5e5; +} + +.header { + display: flex; + align-items: center; +} + +.buttoncontainer { + margin-left: auto; + margin-right: 0; +} + +.details { + padding-left: 20px; + margin-top: 10px; +} + +.notes, +.images, +.urls, +.subChecklists { + margin-top: 10px; +} + +.notes ul, +.images ul, +.urls ul { + list-style-type: none; + padding-left: 0; +} + +.notes li, +.images li, +.urls li { + margin-bottom: 5px; +} + +.notes li strong { + display: block; +} + +.timestamp { + display: block; + font-size: 12px; + color: #888; +} + +.images img { + max-width: 100%; + height: auto; +} + +.item button { + min-width: 50px; + height: 32px; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.item button.yes { + background-color:#aa94d1; + color: white; +} + +.item button.yesset { + background-color:rebeccapurple; + color: white; +} + +.item button.no { + background-color: silver; + color: white; +} + +.item button.noset { + background-color: grey; + color: white; +} + +.item button:hover { + opacity: 0.9; +} diff --git a/app/components/ReproChecklists/ChecklistItem.js b/app/components/ReproChecklists/ChecklistItem.js new file mode 100644 index 0000000..3295803 --- /dev/null +++ b/app/components/ReproChecklists/ChecklistItem.js @@ -0,0 +1,163 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import styles from './ChecklistItem.css'; +import NoteEditor from '../NoteEditor/NoteEditor'; + +function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNote }) { + const [answer, setAnswer] = useState(item.answer); + const [notes, setNotes] = useState(item.userNotes || []); + + const handleNoteUpdate = (note, text) => { + if (note) { + if (onUpdatedNote) { + onUpdatedNote(project, text, note); + } + } else { + if (onAddedNote) { + onAddedNote(project, text); + } + } + }; + + const handleNoteDelete = (note) => { + if (onDeletedNote) { + onDeletedNote(project, note); + } + }; + + useEffect(() => { + setNotes(item.userNotes || []); + }, [item.userNotes]); + + return ( +
+
+ {item.statement} +
+ + +
+
+
+ {notes.length > 0 && ( +
+

Notes:

+
    + {notes.map((note) => ( +
  • + {note.author}: {note.content} + {note.updated} +
  • + ))} +
+
+ )} + + {item.attachedImages.length > 0 && ( +
+

Attached Images:

+
    + {item.attachedImages.map((image) => ( +
  • + attached + {image.updated} +
  • + ))} +
+
+ )} + {item.attachedURLs.length > 0 && ( +
+

Attached URLs:

+ +
+ )} + {item.subChecklists.length > 0 && ( +
+

Sub-Checklists:

+ {item.subChecklists.map((subItem) => ( + + ))} +
+ )} +
+
+ ); +} + +ChecklistItem.propTypes = { + item: PropTypes.shape({ + id: PropTypes.string.isRequired, + statement: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + answer: PropTypes.string.isRequired, + userNotes: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + author: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + content: PropTypes.string.isRequired, + }) + ), + attachedImages: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + data: PropTypes.instanceOf(Blob).isRequired, + updated: PropTypes.string.isRequired, + }) + ), + attachedURLs: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + hyperlink: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + }) + ), + subChecklists: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + statement: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + answer: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + }) + ), + updated: PropTypes.string.isRequired, + }).isRequired, + project: PropTypes.object.isRequired, + onUpdatedNote: PropTypes.func.isRequired, + onDeletedNote: PropTypes.func.isRequired, + onAddedNote: PropTypes.func.isRequired, +}; + +export default ChecklistItem; diff --git a/app/components/ReproChecklists/ReproChecklists.css b/app/components/ReproChecklists/ReproChecklists.css new file mode 100644 index 0000000..aa2d05b --- /dev/null +++ b/app/components/ReproChecklists/ReproChecklists.css @@ -0,0 +1,8 @@ +.container { + padding: 20px; +} + +h2 { + margin-bottom: 20px; + font-size: 24px; +} diff --git a/app/components/ReproChecklists/ReproChecklists.js b/app/components/ReproChecklists/ReproChecklists.js new file mode 100644 index 0000000..e1b87ea --- /dev/null +++ b/app/components/ReproChecklists/ReproChecklists.js @@ -0,0 +1,114 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ChecklistItem from './ChecklistItem'; + +const dummyChecklistItems = [ + { + id: '1', + statement: 'First checklist item statement', + type: 'Type1', + answer: 'yes', + userNotes: [ + { + id: 'note1', + author: 'Author1', + updated: '2024-06-01', + content: 'First note content', + }, + ], + attachedImages: [], + attachedURLs: [], + subChecklists: [], + updated: '2024-06-01', + }, + { + id: '2', + statement: 'Second checklist item statement', + type: 'Type2', + answer: 'no', + userNotes: [ + { + id: 'note2', + author: 'Author2', + updated: '2024-06-02', + content: 'Second note content', + }, + ], + attachedImages: [], + attachedURLs: [], + subChecklists: [], + updated: '2024-06-02', + }, +]; + +function ReproChecklists(props) { + const { project, checklistItems, onUpdatedNote, onDeletedNote, onAddedNote } = props; + + return ( +
+ {checklistItems.map(item => ( + + ))} +
+ ); +} + +ReproChecklists.propTypes = { + project: PropTypes.object.isRequired, + checklistItems: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + statement: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + answer: PropTypes.string.isRequired, + userNotes: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + author: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + content: PropTypes.string.isRequired, + }) + ), + attachedImages: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + data: PropTypes.instanceOf(Blob).isRequired, + updated: PropTypes.string.isRequired, + }) + ), + attachedURLs: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + hyperlink: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + }) + ), + subChecklists: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + statement: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + answer: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + }) + ), + updated: PropTypes.string.isRequired, + }) + ).isRequired, + onUpdatedNote: PropTypes.func.isRequired, + onDeletedNote: PropTypes.func.isRequired, + onAddedNote: PropTypes.func.isRequired, +}; + +ReproChecklists.defaultProps = { + checklistItems: dummyChecklistItems, +}; + +export default ReproChecklists; diff --git a/docs/ReproducibilityChecklists-DataModels.md b/docs/ReproducibilityChecklists-DataModels.md new file mode 100644 index 0000000..0d77350 --- /dev/null +++ b/docs/ReproducibilityChecklists-DataModels.md @@ -0,0 +1,80 @@ +## Checklist Object + +### About + +The **Checklist Object** represents a single checklist item within the reproducibility checklist. It encompasses various attributes and sub-objects to capture different aspects such as statement, type, answer, user notes, attached images, attached URLs, and sub-checklists. + +### Attributes + +| Attribute | Type | Description | +| ------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the checklist item. | +| `statement` | String | The statement or question associated with the checklist item. | +| `type` | String | Indicates the type of checklist item, such as boolean or descriptive. | +| `answer` | String | Stores the user's response to the checklist item. ('yes'/'no' for boolean type, 'answer description' for descriptive type) | +| `userNotes` | Array ([]Note)| An array containing user-added notes associated with the checklist item. | +| `attachedImages` | Array ([]Image)| An array containing image data of images attached to the checklist item. | +| `attachedURLs` | Array ([]URL)| An array containing URLs attached to the checklist item. | +| `subChecklists` | Array ([]Sub_checklist)| An array containing sub-checklists associated with the checklist item. | +| `updated` | String | The timestamp indicating when the checklist item was last updated. | + +### Sub-Checklist Object + +#### About + +The **Sub-Checklist Object** represents a sub-item within a checklist item. It shares similar attributes with the Checklist Object, except for attachments, but is nested within the parent checklist item. + +#### Attributes + +| Attribute | Type | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the sub-checklist item. | +| `statement` | String | The statement or question associated with the sub-checklist item. | +| `type` | String | Indicates the type of sub-checklist item, such as boolean or descriptive. | +| `answer` | String | Stores the user's response to the sub-checklist item. ('yes'/'no' for boolean type, 'answer description' for descriptive type) | +| `updated` | String | The timestamp indicating when the sub-checklist item was last updated. | + +### Note Object + +#### About + +The **Note Object** stores user-added note associated with checklist items. + +#### Attributes + +| Attribute | Type | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the note. | +| `author` | String | A display name taken from StatWrap to indicate who created the note. | +| `updated` | String | The timestamp indicating when the note was last updated. | +| `content` | String | The text content of the note. | + +### Image Object + +#### About + +The **Image Object** stores image data of the image attached to checklist items. + +#### Attributes + +| Attribute | Type | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the image. | +| `data` | Blob | The image data of the attached image. | +| `updated` | String | The timestamp indicating when the image was last updated. | + +### URL Object + +#### About + +The **URL Object** stores hyperlink of the URL attached to checklist items. + +#### Attributes + +| Attribute | Type | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the URL. | +| `hyperlink` | String | The hyperlink associated with the URL. | +| `updated` | String | The timestamp indicating when the URL was last updated. | + +This documentation outlines the structure and attributes of various objects involved in the reproducibility checklist project. From 582e3b22131fab229149bcded3dae536fb1be170 Mon Sep 17 00:00:00 2001 From: AdiAkhileshSingh15 Date: Wed, 26 Jun 2024 19:58:37 +0530 Subject: [PATCH 03/43] minor corrections Signed-off-by: AdiAkhileshSingh15 --- app/components/Project/Project.js | 8 ++++---- app/components/ReproChecklists/ChecklistItem.css | 2 +- app/components/ReproChecklists/ChecklistItem.js | 15 +-------------- app/components/ReproChecklists/ReproChecklists.js | 3 +++ docs/ReproducibilityChecklists-DataModels.md | 1 + 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/app/components/Project/Project.js b/app/components/Project/Project.js index 56c790e..7a11409 100644 --- a/app/components/Project/Project.js +++ b/app/components/Project/Project.js @@ -682,7 +682,7 @@ class Project extends Component { /> ) : null; - const reproChecklists = { - + @@ -738,8 +738,8 @@ class Project extends Component { {projectLog} - - {reproChecklists} + + {Checklists} ); diff --git a/app/components/ReproChecklists/ChecklistItem.css b/app/components/ReproChecklists/ChecklistItem.css index dc06332..fe4647a 100644 --- a/app/components/ReproChecklists/ChecklistItem.css +++ b/app/components/ReproChecklists/ChecklistItem.css @@ -8,7 +8,7 @@ align-items: center; } -.buttoncontainer { +.buttonContainer { margin-left: auto; margin-right: 0; } diff --git a/app/components/ReproChecklists/ChecklistItem.js b/app/components/ReproChecklists/ChecklistItem.js index 3295803..382719d 100644 --- a/app/components/ReproChecklists/ChecklistItem.js +++ b/app/components/ReproChecklists/ChecklistItem.js @@ -33,7 +33,7 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot
{item.statement} -
+
- {notes.length > 0 && ( -
-

Notes:

-
    - {notes.map((note) => ( -
  • - {note.author}: {note.content} - {note.updated} -
  • - ))} -
-
- )} + Reproducibility Checklists +
{checklistItems.map(item => ( Date: Tue, 6 Aug 2024 17:18:54 +0530 Subject: [PATCH 04/43] adds image and url component UI --- .../ReproChecklists/ChecklistItem.css | 120 ++++++++++++++++-- .../ReproChecklists/ChecklistItem.js | 66 +++++++--- .../ReproChecklists/ReproChecklists.js | 97 ++++++++++++-- docs/ReproducibilityChecklists-DataModels.md | 41 +++--- 4 files changed, 263 insertions(+), 61 deletions(-) diff --git a/app/components/ReproChecklists/ChecklistItem.css b/app/components/ReproChecklists/ChecklistItem.css index fe4647a..a1ce89b 100644 --- a/app/components/ReproChecklists/ChecklistItem.css +++ b/app/components/ReproChecklists/ChecklistItem.css @@ -3,8 +3,12 @@ border-bottom: 1px solid #e5e5e5; } +.header span { + max-width: 83%; +} + .header { - display: flex; + display:flex; align-items: center; } @@ -18,28 +22,47 @@ margin-top: 10px; } -.notes, .images, .urls, .subChecklists { margin-top: 10px; } -.notes ul, -.images ul, +.images ul{ + list-style-type: none; + padding-left: 0; + display: flex; + overflow-x: auto; + white-space: nowrap; +} + .urls ul { list-style-type: none; padding-left: 0; + max-height: 200px; + overflow-y: auto; + white-space: nowrap; } -.notes li, -.images li, -.urls li { +.images li{ + margin-right: 10px; margin-bottom: 5px; + padding-right: 5px; + position: relative; } -.notes li strong { - display: block; +.urls li{ + margin-right: 10px; + margin-bottom: 5px; + padding-bottom: 5px; + position: relative; +} + +.images li:not(:last-child) { + border-right: 1px solid #ccc; +} +.urls li:not(:last-child) { + border-bottom: 1px solid #ccc; } .timestamp { @@ -49,8 +72,40 @@ } .images img { - max-width: 100%; height: auto; + max-height: 150px; + transition: max-height 0.3s ease; + display: block; + margin-top: 5px; +} + +.imageContent, +.urlContent { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; +} + +.imageContent.show, +.urlContent.show { + max-height: 500px; +} + +.urls a { + text-decoration: none; + color: #007bff; + display: block; + margin-top: 5px; +} + +.urls a:hover { + color: darkblue; +} + +.images p, +.urls p { + margin-top: 8px; + font-size: 15px; } .item button { @@ -63,12 +118,12 @@ } .item button.yes { - background-color:#aa94d1; + background-color: #aa94d1; color: white; } .item button.yesset { - background-color:rebeccapurple; + background-color: rebeccapurple; color: white; } @@ -85,3 +140,44 @@ .item button:hover { opacity: 0.9; } + +.headerWithButton { + display: flex; + align-items: center; + justify-content: space-between; +} + +.dropdownButton { + background-color: #f8f9fa; + color: #333; + border: none; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + font-size: 14px; + transition: background-color 0.3s ease; +} + +.dropdownButton:hover { + background-color: #e9ecef; +} + +.dropdownContent { + display: none; + padding: 10px; + background-color: #f8f9fa; + border: 1px solid #ccc; + border-radius: 4px; + margin-top: 5px; + transition: max-height 0.3s ease; +} + +.dropdownContent.show { + display: block; + max-height: 500px; +} + +.imageTitle, +.urlTitle { + font-size: 16px; +} diff --git a/app/components/ReproChecklists/ChecklistItem.js b/app/components/ReproChecklists/ChecklistItem.js index 382719d..f19e1b8 100644 --- a/app/components/ReproChecklists/ChecklistItem.js +++ b/app/components/ReproChecklists/ChecklistItem.js @@ -6,6 +6,8 @@ import NoteEditor from '../NoteEditor/NoteEditor'; function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNote }) { const [answer, setAnswer] = useState(item.answer); const [notes, setNotes] = useState(item.userNotes || []); + const [showImages, setShowImages] = useState(false); + const [showURLs, setShowURLs] = useState(false); const handleNoteUpdate = (note, text) => { if (note) { @@ -56,30 +58,48 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot /> {item.attachedImages.length > 0 && (
-

Attached Images:

-
    - {item.attachedImages.map((image) => ( -
  • - attached - {image.updated} -
  • - ))} -
+
+

Attached Images:

+ +
+
+
    + {item.attachedImages.map((image) => ( +
  • + {image.title} + attached +

    {image.description}

    + {image.updated} +
  • + ))} +
+
)} {item.attachedURLs.length > 0 && (
-

Attached URLs:

- +
+

Attached URLs:

+ +
+
+
    + {item.attachedURLs.map((url) => ( +
  • + {url.title} + + {url.hyperlink} + +

    {url.description}

    + {url.updated} +
  • + ))} +
+
)} {item.subChecklists.length > 0 && ( @@ -119,7 +139,9 @@ ChecklistItem.propTypes = { attachedImages: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, - data: PropTypes.instanceOf(Blob).isRequired, + uri: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, updated: PropTypes.string.isRequired, }) ), @@ -127,6 +149,8 @@ ChecklistItem.propTypes = { PropTypes.shape({ id: PropTypes.string.isRequired, hyperlink: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, updated: PropTypes.string.isRequired, }) ), diff --git a/app/components/ReproChecklists/ReproChecklists.js b/app/components/ReproChecklists/ReproChecklists.js index c5732af..a8a0de8 100644 --- a/app/components/ReproChecklists/ReproChecklists.js +++ b/app/components/ReproChecklists/ReproChecklists.js @@ -1,12 +1,12 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import ChecklistItem from './ChecklistItem'; import { Typography } from '@mui/material'; - +// /home/adi/Pictures/Screenshots/maskedvt.png const dummyChecklistItems = [ { id: '1', - statement: 'First checklist item statement', + statement: 'First checklist item statement. Now, I am just trying to make this statement a bit longer to see how it looks in the UI, without making any sense.', type: 'Type1', answer: 'yes', userNotes: [ @@ -17,8 +17,52 @@ const dummyChecklistItems = [ content: 'First note content', }, ], - attachedImages: [], - attachedURLs: [], + attachedImages: [ + { + id: 'image1', + uri: '/home/adi/Pictures/Screenshots/maskedvt.png', + title: 'Dummy Image', + description: 'This is a dummy image description', + updated: '2024-06-09', + }, + { + id: 'image1', + uri: '/home/adi/Pictures/Screenshots/maskedvt.png', + title: 'Dummy Image', + description: 'This is a dummy image description', + updated: '2024-06-09', + }, + { + id: 'image1', + uri: '/home/adi/Pictures/Screenshots/maskedvt.png', + title: 'Dummy Image', + description: 'This is a dummy image description', + updated: '2024-06-09', + }, + ], + attachedURLs: [ + { + id: 'url1', + hyperlink: 'https://github.com/StatTag/StatWrap/blob/master/app/services/user.js', + title: 'StatWrap URL', + description: 'This is URL description', + updated: '2024-06-09', + }, + { + id: 'url1', + hyperlink: 'https://github.com/StatTag/StatWrap/blob/master/app/services/user.js', + title: 'StatWrap URL', + description: 'This is URL description', + updated: '2024-06-09', + }, + { + id: 'url1', + hyperlink: 'https://github.com/StatTag/StatWrap/blob/master/app/services/user.js', + title: 'StatWrap URL', + description: 'This is URL description', + updated: '2024-06-09', + }, + ], subChecklists: [], updated: '2024-06-01', }, @@ -43,7 +87,38 @@ const dummyChecklistItems = [ ]; function ReproChecklists(props) { - const { project, checklistItems, onUpdatedNote, onDeletedNote, onAddedNote } = props; + const { project, reproChecklist, onUpdatedNote, onDeletedNote, onAddedNote } = props; + const [checklistItems, setChecklistItems] = useState(dummyChecklistItems); + + useEffect(() => { + if (project && Array.isArray(project.assets)) { + const imageAssets = []; + + const findImageAssets = (asset) => { + if (asset.type === 'image') { + imageAssets.push(asset); + } + if (asset.children) { + asset.children.forEach(findImageAssets); + } + }; + + project.assets.forEach(findImageAssets); + + const updatedItems = dummyChecklistItems.map(item => ({ + ...item, + attachedImages: imageAssets.map(asset => ({ + id: asset.id, + uri: asset.uri, + title: asset.title || 'Image', + description: asset.description || 'About Image', + updated: asset.updated || 'NA', + })), + })); + + setChecklistItems(updatedItems); + } + }, [project]); return (
@@ -65,7 +140,7 @@ function ReproChecklists(props) { ReproChecklists.propTypes = { project: PropTypes.object.isRequired, - checklistItems: PropTypes.arrayOf( + reproChecklist: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, @@ -82,7 +157,9 @@ ReproChecklists.propTypes = { attachedImages: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, - data: PropTypes.instanceOf(Blob).isRequired, + uri: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, updated: PropTypes.string.isRequired, }) ), @@ -90,6 +167,8 @@ ReproChecklists.propTypes = { PropTypes.shape({ id: PropTypes.string.isRequired, hyperlink: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, updated: PropTypes.string.isRequired, }) ), @@ -111,7 +190,7 @@ ReproChecklists.propTypes = { }; ReproChecklists.defaultProps = { - checklistItems: dummyChecklistItems, + reproChecklist: dummyChecklistItems, }; export default ReproChecklists; diff --git a/docs/ReproducibilityChecklists-DataModels.md b/docs/ReproducibilityChecklists-DataModels.md index 62b4286..a60cd4f 100644 --- a/docs/ReproducibilityChecklists-DataModels.md +++ b/docs/ReproducibilityChecklists-DataModels.md @@ -8,15 +8,15 @@ The **Checklist Object** represents a single checklist item within the reproduci | Attribute | Type | Description | | ------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the checklist item. | -| `statement` | String | The statement or question associated with the checklist item. | -| `type` | String | Indicates the type of checklist item, such as boolean or descriptive. | +| `id` | UUID | A generated unique identifier for the checklist item. | +| `statement` | String | The statement or question associated with the checklist item. | +| `type` | String | Indicates the type of checklist item, such as boolean or descriptive. | | `answer` | String | Stores the user's response to the checklist item. ('yes'/'no' for boolean type, 'answer description' for descriptive type) | -| `userNotes` | Array ([]Note)| An array containing user-added notes associated with the checklist item. | -| `attachedImages` | Array ([]Image)| An array containing image data of images attached to the checklist item. | -| `attachedURLs` | Array ([]URL)| An array containing URLs attached to the checklist item. | -| `subChecklists` | Array ([]Sub_checklist)| An array containing sub-checklists associated with the checklist item. | -| `updated` | String | The timestamp indicating when the checklist item was last updated. | +| `userNotes` | Array ([]Note)| An array containing user-added notes associated with the checklist item. | +| `attachedImages` | Array ([]Image)| An array containing image data of images attached to the checklist item. | +| `attachedURLs` | Array ([]URL)| An array containing URLs attached to the checklist item. | +| `subChecklists` | Array ([]Sub_checklist)| An array containing sub-checklists associated with the checklist item. | +| `updated` | String | The timestamp indicating when the checklist item was last updated. | ### Sub-Checklist Object @@ -26,9 +26,9 @@ The **Sub-Checklist Object** represents a sub-item within a checklist item. It s #### Attributes -| Attribute | Type | Description | -| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the sub-checklist item. | +| Attribute | Type | Description | +| --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the sub-checklist item. | | `statement` | String | The statement or question associated with the sub-checklist item. | | `type` | String | Indicates the type of sub-checklist item, such as boolean or descriptive. | | `answer` | String | Stores the user's response to the sub-checklist item. ('yes'/'no' for boolean type, 'answer description' for descriptive type) | @@ -45,9 +45,9 @@ This follows the same structure as the existing [notes data type](https://github | Attribute | Type | Description | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the note. | -| `author` | String | A display name taken from StatWrap to indicate who created the note. | -| `updated` | String | The timestamp indicating when the note was last updated. | +| `id` | UUID | A generated unique identifier for the note. | +| `author` | String | A display name taken from StatWrap to indicate who created the note. | +| `updated` | String | The timestamp indicating when the note was last updated. | | `content` | String | The text content of the note. | ### Image Object @@ -60,9 +60,10 @@ The **Image Object** stores image data of the image attached to checklist items. | Attribute | Type | Description | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the image. | -| `data` | Blob | The image data of the attached image. | -| `updated` | String | The timestamp indicating when the image was last updated. | +| `id` | UUID | A generated unique identifier for the image. | +| `title` | String | The title of the image. | +| `description` | String | A brief description of the image. | +| `updated` | String | The timestamp indicating when the image was last updated. | ### URL Object @@ -74,8 +75,10 @@ The **URL Object** stores hyperlink of the URL attached to checklist items. | Attribute | Type | Description | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the URL. | +| `id` | UUID | A generated unique identifier for the URL. | | `hyperlink` | String | The hyperlink associated with the URL. | -| `updated` | String | The timestamp indicating when the URL was last updated. | +| `title` | String | The title of the URL. | +| `description` | String | A brief description of the URL. | +| `updated` | String | The timestamp indicating when the URL was last updated. | This documentation outlines the structure and attributes of various objects involved in the reproducibility checklist project. From 01e546e92ffd25988efab4d5289e16658693d834 Mon Sep 17 00:00:00 2001 From: AdiAkhileshSingh15 Date: Fri, 30 Aug 2024 19:37:24 +0530 Subject: [PATCH 05/43] data structure modification and method to list project languages --- app/components/ProjectNotes/ProjectNotes.js | 2 +- .../ReproChecklists/ChecklistItem.js | 57 ++++++++-- .../ReproChecklists/ReproChecklists.js | 105 ++++++++++-------- app/utils/asset.js | 2 +- 4 files changed, 109 insertions(+), 57 deletions(-) diff --git a/app/components/ProjectNotes/ProjectNotes.js b/app/components/ProjectNotes/ProjectNotes.js index 6df8f1f..7286e4f 100644 --- a/app/components/ProjectNotes/ProjectNotes.js +++ b/app/components/ProjectNotes/ProjectNotes.js @@ -157,7 +157,7 @@ function projectNotes(props) { }); } - const mappedPersonNotes = []; + let mappedPersonNotes = []; if (props.project.people) { props.project.people.forEach((p) => { if (p && p.notes) { diff --git a/app/components/ReproChecklists/ChecklistItem.js b/app/components/ReproChecklists/ChecklistItem.js index f19e1b8..1df4748 100644 --- a/app/components/ReproChecklists/ChecklistItem.js +++ b/app/components/ReproChecklists/ChecklistItem.js @@ -8,6 +8,7 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot const [notes, setNotes] = useState(item.userNotes || []); const [showImages, setShowImages] = useState(false); const [showURLs, setShowURLs] = useState(false); + const [allImages, setAllImages] = useState([]); const handleNoteUpdate = (note, text) => { if (note) { @@ -27,6 +28,24 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot } }; + useEffect(() => { + if (project && Array.isArray(project.assets)) { + const imageAssets = []; + const findImageAssets = (asset) => { + if (asset.type === 'image') { + imageAssets.push(asset.uri); + } + if (asset.children) { + asset.children.forEach(findImageAssets); + } + }; + + setAllImages(imageAssets); + + project.assets.forEach(findImageAssets); + } + }, [project]); + useEffect(() => { setNotes(item.userNotes || []); }, [item.userNotes]); @@ -37,19 +56,37 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot {item.statement}
+
+ {item.scanResult && + Object.keys(item.scanResult).map((key) => { + return ( +
+ {key} +
    + {item.scanResult[key].length ? ( + item.scanResult[key].map((answer) => ( +
  • {answer}
  • + )) + ) : ( +
  • No answers available
  • + )} +
+
+ )})} +
{ - if (project && Array.isArray(project.assets)) { - const imageAssets = []; - - const findImageAssets = (asset) => { - if (asset.type === 'image') { - imageAssets.push(asset); - } - if (asset.children) { - asset.children.forEach(findImageAssets); +function findAssetLanguagesAndDependencies(asset) { + if (asset.contentTypes.includes('code') && asset.type === 'file') { + const lastSep = asset.uri.lastIndexOf(path.sep); + const fileName = asset.uri.substring(lastSep + 1); + const ext = fileName.split('.').pop(); + if(ext){ + AssetsConfig.contentTypes.forEach((contentType) => { + if(contentType.extensions.includes(ext)) { + languages.add(contentType.name); } - }; + }); + } + } - project.assets.forEach(findImageAssets); + if(asset.children){ + asset.children.forEach(findAssetLanguagesAndDependencies); + } + + reproducibilityChecklist[2].answerList = {languages: Array.from(languages),dependencies: Array.from(dependencies) }; +} - const updatedItems = dummyChecklistItems.map(item => ({ - ...item, - attachedImages: imageAssets.map(asset => ({ - id: asset.id, - uri: asset.uri, - title: asset.title || 'Image', - description: asset.description || 'About Image', - updated: asset.updated || 'NA', - })), - })); +function ReproChecklists(props) { + const { project, reproChecklist, onUpdatedNote, onDeletedNote, onAddedNote } = props; + const [checklistItems, setChecklistItems] = useState(reproducibilityChecklist); - setChecklistItems(updatedItems); + useEffect(() => { + if (project && project.assets) { + findAssetLanguagesAndDependencies(project.assets); } }, [project]); @@ -145,7 +152,10 @@ ReproChecklists.propTypes = { id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, type: PropTypes.string.isRequired, - answer: PropTypes.string.isRequired, + bool: PropTypes.bool.isRequired, + answerList: PropTypes.objectOf( + PropTypes.arrayOf(PropTypes.string) + ), userNotes: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, @@ -177,11 +187,12 @@ ReproChecklists.propTypes = { id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, type: PropTypes.string.isRequired, - answer: PropTypes.string.isRequired, - updated: PropTypes.string.isRequired, + bool: PropTypes.bool.isRequired, + answerList: PropTypes.objectOf( + PropTypes.arrayOf(PropTypes.string) + ), }) ), - updated: PropTypes.string.isRequired, }) ).isRequired, onUpdatedNote: PropTypes.func.isRequired, @@ -190,7 +201,7 @@ ReproChecklists.propTypes = { }; ReproChecklists.defaultProps = { - reproChecklist: dummyChecklistItems, + reproChecklist: reproducibilityChecklist, }; export default ReproChecklists; diff --git a/app/utils/asset.js b/app/utils/asset.js index afa3256..7a8bd49 100644 --- a/app/utils/asset.js +++ b/app/utils/asset.js @@ -1,5 +1,5 @@ /* eslint-disable no-underscore-dangle */ -import path from 'path'; +const path = require('path'); import last from 'lodash/last'; import constants from '../constants/constants'; From e20caf62c1fc7727bfd70390258b65f04faa26de Mon Sep 17 00:00:00 2001 From: AdiAkhileshSingh15 Date: Fri, 30 Aug 2024 20:05:00 +0530 Subject: [PATCH 06/43] smol fix --- .../ReproChecklists/ReproChecklists.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/components/ReproChecklists/ReproChecklists.js b/app/components/ReproChecklists/ReproChecklists.js index babdb0a..ff62dba 100644 --- a/app/components/ReproChecklists/ReproChecklists.js +++ b/app/components/ReproChecklists/ReproChecklists.js @@ -10,8 +10,8 @@ const reproducibilityChecklist = [ id: '1', statement: 'First checklist item statement. Now, I am just trying to make this statement a bit longer to see how it looks in the UI, without making any sense.', type: 'Type1', - bool: true, - answerList: {}, + answer: true, + scanResult: {}, userNotes: [ { id: 'note1', @@ -66,8 +66,8 @@ const reproducibilityChecklist = [ id: '2', statement: 'Second checklist item statement', type: 'Type2', - bool: false, - answerList: {}, + answer: false, + scanResult: {}, userNotes: [ { id: 'note2', @@ -84,8 +84,8 @@ const reproducibilityChecklist = [ id: '3', statement: 'List of all the languages used and the dependencies of the project', type: 'List', - bool: true, - answerList: {}, + answer: true, + scanResult: {}, userNotes: [], attachedImages: [], attachedURLs: [], @@ -152,8 +152,8 @@ ReproChecklists.propTypes = { id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, type: PropTypes.string.isRequired, - bool: PropTypes.bool.isRequired, - answerList: PropTypes.objectOf( + answer: PropTypes.bool.isRequired, + scanResult: PropTypes.objectOf( PropTypes.arrayOf(PropTypes.string) ), userNotes: PropTypes.arrayOf( @@ -187,8 +187,8 @@ ReproChecklists.propTypes = { id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, type: PropTypes.string.isRequired, - bool: PropTypes.bool.isRequired, - answerList: PropTypes.objectOf( + answer: PropTypes.bool.isRequired, + scanResult: PropTypes.objectOf( PropTypes.arrayOf(PropTypes.string) ), }) From d1f0c2952630fa1d98cd4e9831ef5ded19766a8c Mon Sep 17 00:00:00 2001 From: "Luke R." Date: Tue, 3 Sep 2024 06:37:27 -0500 Subject: [PATCH 07/43] Update ReproducibilityChecklists-Planning.md Fixed formatting --- docs/ReproducibilityChecklists-Planning.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/ReproducibilityChecklists-Planning.md b/docs/ReproducibilityChecklists-Planning.md index 92e7f53..d4b4801 100644 --- a/docs/ReproducibilityChecklists-Planning.md +++ b/docs/ReproducibilityChecklists-Planning.md @@ -14,24 +14,26 @@ StatWrap should be configured to provide information about the following categor 1. **Summarize the software used** - this should include programming language(s) and dependencies. Programming languages can be inferred from file extensions. Dependencies can be inferred from specific references in code files (already processed by StatWrap), as well as dependency files used for specific languages (e.g., `requirements.txt` for Python). StatWrap does not need to determine the specific version used in any environment if it is not specified elsewhere. 2. **Multiple versions of a file** - use a heuristic from file names to determine if the user has versioned files using a naming convention, as opposed to using source control. For example, `main_analysis.R` and `main_analysis_v2.R` or `main_analysis-jd.R`. StatWrap could use metrics other than the file name, such as similarity of file content, to further infer if the files are likely versioned. For a grouping of files that StatWrap thinks are versioned, it should check attributes to: - 1. See if any of the files are marked as 'archived'. - 2. If more than one file is marked as an 'entry point'. -3. **Location of project documentation** - StatWrap should detect the presence of a README file (any extension), and if there is a folder named `doc`, `docs`, or `documentation`. If so, display these file(s)/folder(s) as the projecct documentation. -4. **Absolute paths used** - StatWrap should detect if explicit absolute paths are used within the code files within a project. This should detect across Mac, Linux, and Windows, including multiple ways of denoting paths across OS (e.g., on Windows languages like R will let you reference `C:/test/file.csv` or `C:\test\file.csv`), as well as fully qualified network paths (e.g., `\\research\files\test.csv`). StatWrap should report which file(s) are using absolute paths. -5. **Binary / proprietary file formats for data** - flag data files that are not purely text-based. This can be done from a configureable list of file extensions. -6. **List categories of data files/folders** - using user-assigned attributes, or folder naming conventions, display the list of files associated with different categories of data: + + 1. See if any of the files are marked as 'archived'. + 2. If more than one file is marked as an 'entry point'. + +4. **Location of project documentation** - StatWrap should detect the presence of a README file (any extension), and if there is a folder named `doc`, `docs`, or `documentation`. If so, display these file(s)/folder(s) as the projecct documentation. +5. **Absolute paths used** - StatWrap should detect if explicit absolute paths are used within the code files within a project. This should detect across Mac, Linux, and Windows, including multiple ways of denoting paths across OS (e.g., on Windows languages like R will let you reference `C:/test/file.csv` or `C:\test\file.csv`), as well as fully qualified network paths (e.g., `\\research\files\test.csv`). StatWrap should report which file(s) are using absolute paths. +6. **Binary / proprietary file formats for data** - flag data files that are not purely text-based. This can be done from a configureable list of file extensions. +7. **List categories of data files/folders** - using user-assigned attributes, or folder naming conventions, display the list of files associated with different categories of data: 1. Input data - data flagged with an attribute of "Input / Raw Data", or data contained in folders using a configurable list of folder names (e.g., `raw`, `input`) 2. Derived data - data flagged with an attribute of "Derived", or data contained in folders using a configurable list of folder names (e.g., `output`, `derived`) 3. Data (general) - data files that we cannot otherwise categorize -7. **List categories of code files/folders** - using user-assigned attributes, or folder naming conventions, display the list of code files associated with different processing steps. Note that a single code file may have multiple annotations: +8. **List categories of code files/folders** - using user-assigned attributes, or folder naming conventions, display the list of code files associated with different processing steps. Note that a single code file may have multiple annotations: 1. Data acquisition - used to import / pull data. This would be flagged with an attribute of "Data acquisition". 2. Data processing - this would be flagged with an attribute of "Data processing" 3. Analysis - this would be flagged with an attribute of "Analysis" -8. **Entry points** - StatWrap should list all code files flagged as entry points by the user. If none, and code files exist, this should also be noted ("No entry point has been specified"). This could be combined with the previous item. -9. **External assets** - identify references in the code to: databases, APIs, web URLs that contain data. Summarize these for the user as external assets that are being referenced. +9. **Entry points** - StatWrap should list all code files flagged as entry points by the user. If none, and code files exist, this should also be noted ("No entry point has been specified"). This could be combined with the previous item. +10. **External assets** - identify references in the code to: databases, APIs, web URLs that contain data. Summarize these for the user as external assets that are being referenced. ### 2. Human-entered information / decision making (user) This is more of the traditional "checklist", where the user is presented with a list of questions that they may attest to. Some of the answers may be pre-filled using data collected by StatWrap as defined in the automated information gathering (previous section). -(See proposal by Adi) \ No newline at end of file +(See proposal by Adi) From 84d88878f9b5b830bb9c0d752e8d1b6220c39a06 Mon Sep 17 00:00:00 2001 From: Luke R Date: Tue, 28 May 2024 08:45:11 -0500 Subject: [PATCH 08/43] Added documentation around reproducibility checklist features --- docs/ReproducibilityChecklists-Planning.md | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 docs/ReproducibilityChecklists-Planning.md diff --git a/docs/ReproducibilityChecklists-Planning.md b/docs/ReproducibilityChecklists-Planning.md new file mode 100644 index 0000000..92e7f53 --- /dev/null +++ b/docs/ReproducibilityChecklists-Planning.md @@ -0,0 +1,37 @@ +# Reproducible Research Checklists + +This document provides design guidance and general requirements for the reproducible research checklist feature in StatWrap. + +## Modes of Operation + +There are two modes of operation for the checklist: + +### 1. Automated information gathering / decision making (StatWrap) + +StatWrap will use information from project assets and user-entered metadata in order to provide information about reproducible research practices. This will be used to **inform** users, but will not make a binary indicator (yes/no) about if a practice is "reproducible" or not. + +StatWrap should be configured to provide information about the following categories of data. The way the data could be displayed is noted, but is not intended to limit the final implementation. For each of these, there should be a service method that is able to perform the processed logic. Unlike the actual checklist UI, these items are not configurable by the end user, they are built within StatWrap. + +1. **Summarize the software used** - this should include programming language(s) and dependencies. Programming languages can be inferred from file extensions. Dependencies can be inferred from specific references in code files (already processed by StatWrap), as well as dependency files used for specific languages (e.g., `requirements.txt` for Python). StatWrap does not need to determine the specific version used in any environment if it is not specified elsewhere. +2. **Multiple versions of a file** - use a heuristic from file names to determine if the user has versioned files using a naming convention, as opposed to using source control. For example, `main_analysis.R` and `main_analysis_v2.R` or `main_analysis-jd.R`. StatWrap could use metrics other than the file name, such as similarity of file content, to further infer if the files are likely versioned. For a grouping of files that StatWrap thinks are versioned, it should check attributes to: + 1. See if any of the files are marked as 'archived'. + 2. If more than one file is marked as an 'entry point'. +3. **Location of project documentation** - StatWrap should detect the presence of a README file (any extension), and if there is a folder named `doc`, `docs`, or `documentation`. If so, display these file(s)/folder(s) as the projecct documentation. +4. **Absolute paths used** - StatWrap should detect if explicit absolute paths are used within the code files within a project. This should detect across Mac, Linux, and Windows, including multiple ways of denoting paths across OS (e.g., on Windows languages like R will let you reference `C:/test/file.csv` or `C:\test\file.csv`), as well as fully qualified network paths (e.g., `\\research\files\test.csv`). StatWrap should report which file(s) are using absolute paths. +5. **Binary / proprietary file formats for data** - flag data files that are not purely text-based. This can be done from a configureable list of file extensions. +6. **List categories of data files/folders** - using user-assigned attributes, or folder naming conventions, display the list of files associated with different categories of data: + 1. Input data - data flagged with an attribute of "Input / Raw Data", or data contained in folders using a configurable list of folder names (e.g., `raw`, `input`) + 2. Derived data - data flagged with an attribute of "Derived", or data contained in folders using a configurable list of folder names (e.g., `output`, `derived`) + 3. Data (general) - data files that we cannot otherwise categorize +7. **List categories of code files/folders** - using user-assigned attributes, or folder naming conventions, display the list of code files associated with different processing steps. Note that a single code file may have multiple annotations: + 1. Data acquisition - used to import / pull data. This would be flagged with an attribute of "Data acquisition". + 2. Data processing - this would be flagged with an attribute of "Data processing" + 3. Analysis - this would be flagged with an attribute of "Analysis" +8. **Entry points** - StatWrap should list all code files flagged as entry points by the user. If none, and code files exist, this should also be noted ("No entry point has been specified"). This could be combined with the previous item. +9. **External assets** - identify references in the code to: databases, APIs, web URLs that contain data. Summarize these for the user as external assets that are being referenced. + +### 2. Human-entered information / decision making (user) + +This is more of the traditional "checklist", where the user is presented with a list of questions that they may attest to. Some of the answers may be pre-filled using data collected by StatWrap as defined in the automated information gathering (previous section). + +(See proposal by Adi) \ No newline at end of file From 19eb76833790e8cc8ee0056ef037c0aee77bd7f0 Mon Sep 17 00:00:00 2001 From: AdiAkhileshSingh15 Date: Wed, 26 Jun 2024 13:19:50 +0530 Subject: [PATCH 09/43] basic UI skeleton Signed-off-by: AdiAkhileshSingh15 --- app/components/Project/Project.js | 12 ++ .../ReproChecklists/ChecklistItem.css | 87 ++++++++++ .../ReproChecklists/ChecklistItem.js | 163 ++++++++++++++++++ .../ReproChecklists/ReproChecklists.css | 8 + .../ReproChecklists/ReproChecklists.js | 114 ++++++++++++ docs/ReproducibilityChecklists-DataModels.md | 80 +++++++++ 6 files changed, 464 insertions(+) create mode 100644 app/components/ReproChecklists/ChecklistItem.css create mode 100644 app/components/ReproChecklists/ChecklistItem.js create mode 100644 app/components/ReproChecklists/ReproChecklists.css create mode 100644 app/components/ReproChecklists/ReproChecklists.js create mode 100644 docs/ReproducibilityChecklists-DataModels.md diff --git a/app/components/Project/Project.js b/app/components/Project/Project.js index bfe6d42..56c790e 100644 --- a/app/components/Project/Project.js +++ b/app/components/Project/Project.js @@ -13,6 +13,7 @@ import Assets from './Assets/Assets'; import Workflow from '../Workflow/Workflow'; import People from '../People/People'; import ProjectLog from '../ProjectLog/ProjectLog'; +import ReproChecklists from '../ReproChecklists/ReproChecklists'; import ProjectNotes from '../ProjectNotes/ProjectNotes'; import { ActionType, EntityType, DescriptionContentType } from '../../constants/constants'; import AssetUtil from '../../utils/asset'; @@ -681,6 +682,13 @@ class Project extends Component { /> ) : null; + const reproChecklists = + content = (
@@ -709,6 +717,7 @@ class Project extends Component { +
@@ -729,6 +738,9 @@ class Project extends Component { {projectLog} + + {reproChecklists} +
); } diff --git a/app/components/ReproChecklists/ChecklistItem.css b/app/components/ReproChecklists/ChecklistItem.css new file mode 100644 index 0000000..dc06332 --- /dev/null +++ b/app/components/ReproChecklists/ChecklistItem.css @@ -0,0 +1,87 @@ +.item { + padding: 10px; + border-bottom: 1px solid #e5e5e5; +} + +.header { + display: flex; + align-items: center; +} + +.buttoncontainer { + margin-left: auto; + margin-right: 0; +} + +.details { + padding-left: 20px; + margin-top: 10px; +} + +.notes, +.images, +.urls, +.subChecklists { + margin-top: 10px; +} + +.notes ul, +.images ul, +.urls ul { + list-style-type: none; + padding-left: 0; +} + +.notes li, +.images li, +.urls li { + margin-bottom: 5px; +} + +.notes li strong { + display: block; +} + +.timestamp { + display: block; + font-size: 12px; + color: #888; +} + +.images img { + max-width: 100%; + height: auto; +} + +.item button { + min-width: 50px; + height: 32px; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.item button.yes { + background-color:#aa94d1; + color: white; +} + +.item button.yesset { + background-color:rebeccapurple; + color: white; +} + +.item button.no { + background-color: silver; + color: white; +} + +.item button.noset { + background-color: grey; + color: white; +} + +.item button:hover { + opacity: 0.9; +} diff --git a/app/components/ReproChecklists/ChecklistItem.js b/app/components/ReproChecklists/ChecklistItem.js new file mode 100644 index 0000000..3295803 --- /dev/null +++ b/app/components/ReproChecklists/ChecklistItem.js @@ -0,0 +1,163 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import styles from './ChecklistItem.css'; +import NoteEditor from '../NoteEditor/NoteEditor'; + +function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNote }) { + const [answer, setAnswer] = useState(item.answer); + const [notes, setNotes] = useState(item.userNotes || []); + + const handleNoteUpdate = (note, text) => { + if (note) { + if (onUpdatedNote) { + onUpdatedNote(project, text, note); + } + } else { + if (onAddedNote) { + onAddedNote(project, text); + } + } + }; + + const handleNoteDelete = (note) => { + if (onDeletedNote) { + onDeletedNote(project, note); + } + }; + + useEffect(() => { + setNotes(item.userNotes || []); + }, [item.userNotes]); + + return ( +
+
+ {item.statement} +
+ + +
+
+
+ {notes.length > 0 && ( +
+

Notes:

+
    + {notes.map((note) => ( +
  • + {note.author}: {note.content} + {note.updated} +
  • + ))} +
+
+ )} + + {item.attachedImages.length > 0 && ( +
+

Attached Images:

+
    + {item.attachedImages.map((image) => ( +
  • + attached + {image.updated} +
  • + ))} +
+
+ )} + {item.attachedURLs.length > 0 && ( +
+

Attached URLs:

+ +
+ )} + {item.subChecklists.length > 0 && ( +
+

Sub-Checklists:

+ {item.subChecklists.map((subItem) => ( + + ))} +
+ )} +
+
+ ); +} + +ChecklistItem.propTypes = { + item: PropTypes.shape({ + id: PropTypes.string.isRequired, + statement: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + answer: PropTypes.string.isRequired, + userNotes: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + author: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + content: PropTypes.string.isRequired, + }) + ), + attachedImages: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + data: PropTypes.instanceOf(Blob).isRequired, + updated: PropTypes.string.isRequired, + }) + ), + attachedURLs: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + hyperlink: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + }) + ), + subChecklists: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + statement: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + answer: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + }) + ), + updated: PropTypes.string.isRequired, + }).isRequired, + project: PropTypes.object.isRequired, + onUpdatedNote: PropTypes.func.isRequired, + onDeletedNote: PropTypes.func.isRequired, + onAddedNote: PropTypes.func.isRequired, +}; + +export default ChecklistItem; diff --git a/app/components/ReproChecklists/ReproChecklists.css b/app/components/ReproChecklists/ReproChecklists.css new file mode 100644 index 0000000..aa2d05b --- /dev/null +++ b/app/components/ReproChecklists/ReproChecklists.css @@ -0,0 +1,8 @@ +.container { + padding: 20px; +} + +h2 { + margin-bottom: 20px; + font-size: 24px; +} diff --git a/app/components/ReproChecklists/ReproChecklists.js b/app/components/ReproChecklists/ReproChecklists.js new file mode 100644 index 0000000..e1b87ea --- /dev/null +++ b/app/components/ReproChecklists/ReproChecklists.js @@ -0,0 +1,114 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ChecklistItem from './ChecklistItem'; + +const dummyChecklistItems = [ + { + id: '1', + statement: 'First checklist item statement', + type: 'Type1', + answer: 'yes', + userNotes: [ + { + id: 'note1', + author: 'Author1', + updated: '2024-06-01', + content: 'First note content', + }, + ], + attachedImages: [], + attachedURLs: [], + subChecklists: [], + updated: '2024-06-01', + }, + { + id: '2', + statement: 'Second checklist item statement', + type: 'Type2', + answer: 'no', + userNotes: [ + { + id: 'note2', + author: 'Author2', + updated: '2024-06-02', + content: 'Second note content', + }, + ], + attachedImages: [], + attachedURLs: [], + subChecklists: [], + updated: '2024-06-02', + }, +]; + +function ReproChecklists(props) { + const { project, checklistItems, onUpdatedNote, onDeletedNote, onAddedNote } = props; + + return ( +
+ {checklistItems.map(item => ( + + ))} +
+ ); +} + +ReproChecklists.propTypes = { + project: PropTypes.object.isRequired, + checklistItems: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + statement: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + answer: PropTypes.string.isRequired, + userNotes: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + author: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + content: PropTypes.string.isRequired, + }) + ), + attachedImages: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + data: PropTypes.instanceOf(Blob).isRequired, + updated: PropTypes.string.isRequired, + }) + ), + attachedURLs: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + hyperlink: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + }) + ), + subChecklists: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + statement: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + answer: PropTypes.string.isRequired, + updated: PropTypes.string.isRequired, + }) + ), + updated: PropTypes.string.isRequired, + }) + ).isRequired, + onUpdatedNote: PropTypes.func.isRequired, + onDeletedNote: PropTypes.func.isRequired, + onAddedNote: PropTypes.func.isRequired, +}; + +ReproChecklists.defaultProps = { + checklistItems: dummyChecklistItems, +}; + +export default ReproChecklists; diff --git a/docs/ReproducibilityChecklists-DataModels.md b/docs/ReproducibilityChecklists-DataModels.md new file mode 100644 index 0000000..0d77350 --- /dev/null +++ b/docs/ReproducibilityChecklists-DataModels.md @@ -0,0 +1,80 @@ +## Checklist Object + +### About + +The **Checklist Object** represents a single checklist item within the reproducibility checklist. It encompasses various attributes and sub-objects to capture different aspects such as statement, type, answer, user notes, attached images, attached URLs, and sub-checklists. + +### Attributes + +| Attribute | Type | Description | +| ------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the checklist item. | +| `statement` | String | The statement or question associated with the checklist item. | +| `type` | String | Indicates the type of checklist item, such as boolean or descriptive. | +| `answer` | String | Stores the user's response to the checklist item. ('yes'/'no' for boolean type, 'answer description' for descriptive type) | +| `userNotes` | Array ([]Note)| An array containing user-added notes associated with the checklist item. | +| `attachedImages` | Array ([]Image)| An array containing image data of images attached to the checklist item. | +| `attachedURLs` | Array ([]URL)| An array containing URLs attached to the checklist item. | +| `subChecklists` | Array ([]Sub_checklist)| An array containing sub-checklists associated with the checklist item. | +| `updated` | String | The timestamp indicating when the checklist item was last updated. | + +### Sub-Checklist Object + +#### About + +The **Sub-Checklist Object** represents a sub-item within a checklist item. It shares similar attributes with the Checklist Object, except for attachments, but is nested within the parent checklist item. + +#### Attributes + +| Attribute | Type | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the sub-checklist item. | +| `statement` | String | The statement or question associated with the sub-checklist item. | +| `type` | String | Indicates the type of sub-checklist item, such as boolean or descriptive. | +| `answer` | String | Stores the user's response to the sub-checklist item. ('yes'/'no' for boolean type, 'answer description' for descriptive type) | +| `updated` | String | The timestamp indicating when the sub-checklist item was last updated. | + +### Note Object + +#### About + +The **Note Object** stores user-added note associated with checklist items. + +#### Attributes + +| Attribute | Type | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the note. | +| `author` | String | A display name taken from StatWrap to indicate who created the note. | +| `updated` | String | The timestamp indicating when the note was last updated. | +| `content` | String | The text content of the note. | + +### Image Object + +#### About + +The **Image Object** stores image data of the image attached to checklist items. + +#### Attributes + +| Attribute | Type | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the image. | +| `data` | Blob | The image data of the attached image. | +| `updated` | String | The timestamp indicating when the image was last updated. | + +### URL Object + +#### About + +The **URL Object** stores hyperlink of the URL attached to checklist items. + +#### Attributes + +| Attribute | Type | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the URL. | +| `hyperlink` | String | The hyperlink associated with the URL. | +| `updated` | String | The timestamp indicating when the URL was last updated. | + +This documentation outlines the structure and attributes of various objects involved in the reproducibility checklist project. From a3599a738e2652086f4c50c8fc986bfa1b1eb137 Mon Sep 17 00:00:00 2001 From: AdiAkhileshSingh15 Date: Wed, 26 Jun 2024 19:58:37 +0530 Subject: [PATCH 10/43] minor corrections Signed-off-by: AdiAkhileshSingh15 --- app/components/Project/Project.js | 8 ++++---- app/components/ReproChecklists/ChecklistItem.css | 2 +- app/components/ReproChecklists/ChecklistItem.js | 15 +-------------- app/components/ReproChecklists/ReproChecklists.js | 3 +++ docs/ReproducibilityChecklists-DataModels.md | 1 + 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/app/components/Project/Project.js b/app/components/Project/Project.js index 56c790e..7a11409 100644 --- a/app/components/Project/Project.js +++ b/app/components/Project/Project.js @@ -682,7 +682,7 @@ class Project extends Component { /> ) : null; - const reproChecklists = { - +
@@ -738,8 +738,8 @@ class Project extends Component { {projectLog} - - {reproChecklists} + + {Checklists} ); diff --git a/app/components/ReproChecklists/ChecklistItem.css b/app/components/ReproChecklists/ChecklistItem.css index dc06332..fe4647a 100644 --- a/app/components/ReproChecklists/ChecklistItem.css +++ b/app/components/ReproChecklists/ChecklistItem.css @@ -8,7 +8,7 @@ align-items: center; } -.buttoncontainer { +.buttonContainer { margin-left: auto; margin-right: 0; } diff --git a/app/components/ReproChecklists/ChecklistItem.js b/app/components/ReproChecklists/ChecklistItem.js index 3295803..382719d 100644 --- a/app/components/ReproChecklists/ChecklistItem.js +++ b/app/components/ReproChecklists/ChecklistItem.js @@ -33,7 +33,7 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot
{item.statement} -
+
- {notes.length > 0 && ( -
-

Notes:

-
    - {notes.map((note) => ( -
  • - {note.author}: {note.content} - {note.updated} -
  • - ))} -
-
- )} + Reproducibility Checklists +
{checklistItems.map(item => ( Date: Tue, 6 Aug 2024 17:18:54 +0530 Subject: [PATCH 11/43] adds image and url component UI --- .../ReproChecklists/ChecklistItem.css | 120 ++++++++++++++++-- .../ReproChecklists/ChecklistItem.js | 66 +++++++--- .../ReproChecklists/ReproChecklists.js | 97 ++++++++++++-- docs/ReproducibilityChecklists-DataModels.md | 41 +++--- 4 files changed, 263 insertions(+), 61 deletions(-) diff --git a/app/components/ReproChecklists/ChecklistItem.css b/app/components/ReproChecklists/ChecklistItem.css index fe4647a..a1ce89b 100644 --- a/app/components/ReproChecklists/ChecklistItem.css +++ b/app/components/ReproChecklists/ChecklistItem.css @@ -3,8 +3,12 @@ border-bottom: 1px solid #e5e5e5; } +.header span { + max-width: 83%; +} + .header { - display: flex; + display:flex; align-items: center; } @@ -18,28 +22,47 @@ margin-top: 10px; } -.notes, .images, .urls, .subChecklists { margin-top: 10px; } -.notes ul, -.images ul, +.images ul{ + list-style-type: none; + padding-left: 0; + display: flex; + overflow-x: auto; + white-space: nowrap; +} + .urls ul { list-style-type: none; padding-left: 0; + max-height: 200px; + overflow-y: auto; + white-space: nowrap; } -.notes li, -.images li, -.urls li { +.images li{ + margin-right: 10px; margin-bottom: 5px; + padding-right: 5px; + position: relative; } -.notes li strong { - display: block; +.urls li{ + margin-right: 10px; + margin-bottom: 5px; + padding-bottom: 5px; + position: relative; +} + +.images li:not(:last-child) { + border-right: 1px solid #ccc; +} +.urls li:not(:last-child) { + border-bottom: 1px solid #ccc; } .timestamp { @@ -49,8 +72,40 @@ } .images img { - max-width: 100%; height: auto; + max-height: 150px; + transition: max-height 0.3s ease; + display: block; + margin-top: 5px; +} + +.imageContent, +.urlContent { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; +} + +.imageContent.show, +.urlContent.show { + max-height: 500px; +} + +.urls a { + text-decoration: none; + color: #007bff; + display: block; + margin-top: 5px; +} + +.urls a:hover { + color: darkblue; +} + +.images p, +.urls p { + margin-top: 8px; + font-size: 15px; } .item button { @@ -63,12 +118,12 @@ } .item button.yes { - background-color:#aa94d1; + background-color: #aa94d1; color: white; } .item button.yesset { - background-color:rebeccapurple; + background-color: rebeccapurple; color: white; } @@ -85,3 +140,44 @@ .item button:hover { opacity: 0.9; } + +.headerWithButton { + display: flex; + align-items: center; + justify-content: space-between; +} + +.dropdownButton { + background-color: #f8f9fa; + color: #333; + border: none; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + font-size: 14px; + transition: background-color 0.3s ease; +} + +.dropdownButton:hover { + background-color: #e9ecef; +} + +.dropdownContent { + display: none; + padding: 10px; + background-color: #f8f9fa; + border: 1px solid #ccc; + border-radius: 4px; + margin-top: 5px; + transition: max-height 0.3s ease; +} + +.dropdownContent.show { + display: block; + max-height: 500px; +} + +.imageTitle, +.urlTitle { + font-size: 16px; +} diff --git a/app/components/ReproChecklists/ChecklistItem.js b/app/components/ReproChecklists/ChecklistItem.js index 382719d..f19e1b8 100644 --- a/app/components/ReproChecklists/ChecklistItem.js +++ b/app/components/ReproChecklists/ChecklistItem.js @@ -6,6 +6,8 @@ import NoteEditor from '../NoteEditor/NoteEditor'; function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNote }) { const [answer, setAnswer] = useState(item.answer); const [notes, setNotes] = useState(item.userNotes || []); + const [showImages, setShowImages] = useState(false); + const [showURLs, setShowURLs] = useState(false); const handleNoteUpdate = (note, text) => { if (note) { @@ -56,30 +58,48 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot /> {item.attachedImages.length > 0 && (
-

Attached Images:

-
    - {item.attachedImages.map((image) => ( -
  • - attached - {image.updated} -
  • - ))} -
+
+

Attached Images:

+ +
+
+
    + {item.attachedImages.map((image) => ( +
  • + {image.title} + attached +

    {image.description}

    + {image.updated} +
  • + ))} +
+
)} {item.attachedURLs.length > 0 && (
-

Attached URLs:

- +
+

Attached URLs:

+ +
+
+
    + {item.attachedURLs.map((url) => ( +
  • + {url.title} + + {url.hyperlink} + +

    {url.description}

    + {url.updated} +
  • + ))} +
+
)} {item.subChecklists.length > 0 && ( @@ -119,7 +139,9 @@ ChecklistItem.propTypes = { attachedImages: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, - data: PropTypes.instanceOf(Blob).isRequired, + uri: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, updated: PropTypes.string.isRequired, }) ), @@ -127,6 +149,8 @@ ChecklistItem.propTypes = { PropTypes.shape({ id: PropTypes.string.isRequired, hyperlink: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, updated: PropTypes.string.isRequired, }) ), diff --git a/app/components/ReproChecklists/ReproChecklists.js b/app/components/ReproChecklists/ReproChecklists.js index c5732af..a8a0de8 100644 --- a/app/components/ReproChecklists/ReproChecklists.js +++ b/app/components/ReproChecklists/ReproChecklists.js @@ -1,12 +1,12 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import ChecklistItem from './ChecklistItem'; import { Typography } from '@mui/material'; - +// /home/adi/Pictures/Screenshots/maskedvt.png const dummyChecklistItems = [ { id: '1', - statement: 'First checklist item statement', + statement: 'First checklist item statement. Now, I am just trying to make this statement a bit longer to see how it looks in the UI, without making any sense.', type: 'Type1', answer: 'yes', userNotes: [ @@ -17,8 +17,52 @@ const dummyChecklistItems = [ content: 'First note content', }, ], - attachedImages: [], - attachedURLs: [], + attachedImages: [ + { + id: 'image1', + uri: '/home/adi/Pictures/Screenshots/maskedvt.png', + title: 'Dummy Image', + description: 'This is a dummy image description', + updated: '2024-06-09', + }, + { + id: 'image1', + uri: '/home/adi/Pictures/Screenshots/maskedvt.png', + title: 'Dummy Image', + description: 'This is a dummy image description', + updated: '2024-06-09', + }, + { + id: 'image1', + uri: '/home/adi/Pictures/Screenshots/maskedvt.png', + title: 'Dummy Image', + description: 'This is a dummy image description', + updated: '2024-06-09', + }, + ], + attachedURLs: [ + { + id: 'url1', + hyperlink: 'https://github.com/StatTag/StatWrap/blob/master/app/services/user.js', + title: 'StatWrap URL', + description: 'This is URL description', + updated: '2024-06-09', + }, + { + id: 'url1', + hyperlink: 'https://github.com/StatTag/StatWrap/blob/master/app/services/user.js', + title: 'StatWrap URL', + description: 'This is URL description', + updated: '2024-06-09', + }, + { + id: 'url1', + hyperlink: 'https://github.com/StatTag/StatWrap/blob/master/app/services/user.js', + title: 'StatWrap URL', + description: 'This is URL description', + updated: '2024-06-09', + }, + ], subChecklists: [], updated: '2024-06-01', }, @@ -43,7 +87,38 @@ const dummyChecklistItems = [ ]; function ReproChecklists(props) { - const { project, checklistItems, onUpdatedNote, onDeletedNote, onAddedNote } = props; + const { project, reproChecklist, onUpdatedNote, onDeletedNote, onAddedNote } = props; + const [checklistItems, setChecklistItems] = useState(dummyChecklistItems); + + useEffect(() => { + if (project && Array.isArray(project.assets)) { + const imageAssets = []; + + const findImageAssets = (asset) => { + if (asset.type === 'image') { + imageAssets.push(asset); + } + if (asset.children) { + asset.children.forEach(findImageAssets); + } + }; + + project.assets.forEach(findImageAssets); + + const updatedItems = dummyChecklistItems.map(item => ({ + ...item, + attachedImages: imageAssets.map(asset => ({ + id: asset.id, + uri: asset.uri, + title: asset.title || 'Image', + description: asset.description || 'About Image', + updated: asset.updated || 'NA', + })), + })); + + setChecklistItems(updatedItems); + } + }, [project]); return (
@@ -65,7 +140,7 @@ function ReproChecklists(props) { ReproChecklists.propTypes = { project: PropTypes.object.isRequired, - checklistItems: PropTypes.arrayOf( + reproChecklist: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, @@ -82,7 +157,9 @@ ReproChecklists.propTypes = { attachedImages: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, - data: PropTypes.instanceOf(Blob).isRequired, + uri: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, updated: PropTypes.string.isRequired, }) ), @@ -90,6 +167,8 @@ ReproChecklists.propTypes = { PropTypes.shape({ id: PropTypes.string.isRequired, hyperlink: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, updated: PropTypes.string.isRequired, }) ), @@ -111,7 +190,7 @@ ReproChecklists.propTypes = { }; ReproChecklists.defaultProps = { - checklistItems: dummyChecklistItems, + reproChecklist: dummyChecklistItems, }; export default ReproChecklists; diff --git a/docs/ReproducibilityChecklists-DataModels.md b/docs/ReproducibilityChecklists-DataModels.md index 62b4286..a60cd4f 100644 --- a/docs/ReproducibilityChecklists-DataModels.md +++ b/docs/ReproducibilityChecklists-DataModels.md @@ -8,15 +8,15 @@ The **Checklist Object** represents a single checklist item within the reproduci | Attribute | Type | Description | | ------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the checklist item. | -| `statement` | String | The statement or question associated with the checklist item. | -| `type` | String | Indicates the type of checklist item, such as boolean or descriptive. | +| `id` | UUID | A generated unique identifier for the checklist item. | +| `statement` | String | The statement or question associated with the checklist item. | +| `type` | String | Indicates the type of checklist item, such as boolean or descriptive. | | `answer` | String | Stores the user's response to the checklist item. ('yes'/'no' for boolean type, 'answer description' for descriptive type) | -| `userNotes` | Array ([]Note)| An array containing user-added notes associated with the checklist item. | -| `attachedImages` | Array ([]Image)| An array containing image data of images attached to the checklist item. | -| `attachedURLs` | Array ([]URL)| An array containing URLs attached to the checklist item. | -| `subChecklists` | Array ([]Sub_checklist)| An array containing sub-checklists associated with the checklist item. | -| `updated` | String | The timestamp indicating when the checklist item was last updated. | +| `userNotes` | Array ([]Note)| An array containing user-added notes associated with the checklist item. | +| `attachedImages` | Array ([]Image)| An array containing image data of images attached to the checklist item. | +| `attachedURLs` | Array ([]URL)| An array containing URLs attached to the checklist item. | +| `subChecklists` | Array ([]Sub_checklist)| An array containing sub-checklists associated with the checklist item. | +| `updated` | String | The timestamp indicating when the checklist item was last updated. | ### Sub-Checklist Object @@ -26,9 +26,9 @@ The **Sub-Checklist Object** represents a sub-item within a checklist item. It s #### Attributes -| Attribute | Type | Description | -| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the sub-checklist item. | +| Attribute | Type | Description | +| --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | UUID | A generated unique identifier for the sub-checklist item. | | `statement` | String | The statement or question associated with the sub-checklist item. | | `type` | String | Indicates the type of sub-checklist item, such as boolean or descriptive. | | `answer` | String | Stores the user's response to the sub-checklist item. ('yes'/'no' for boolean type, 'answer description' for descriptive type) | @@ -45,9 +45,9 @@ This follows the same structure as the existing [notes data type](https://github | Attribute | Type | Description | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the note. | -| `author` | String | A display name taken from StatWrap to indicate who created the note. | -| `updated` | String | The timestamp indicating when the note was last updated. | +| `id` | UUID | A generated unique identifier for the note. | +| `author` | String | A display name taken from StatWrap to indicate who created the note. | +| `updated` | String | The timestamp indicating when the note was last updated. | | `content` | String | The text content of the note. | ### Image Object @@ -60,9 +60,10 @@ The **Image Object** stores image data of the image attached to checklist items. | Attribute | Type | Description | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the image. | -| `data` | Blob | The image data of the attached image. | -| `updated` | String | The timestamp indicating when the image was last updated. | +| `id` | UUID | A generated unique identifier for the image. | +| `title` | String | The title of the image. | +| `description` | String | A brief description of the image. | +| `updated` | String | The timestamp indicating when the image was last updated. | ### URL Object @@ -74,8 +75,10 @@ The **URL Object** stores hyperlink of the URL attached to checklist items. | Attribute | Type | Description | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | UUID | A generated unique identifier for the URL. | +| `id` | UUID | A generated unique identifier for the URL. | | `hyperlink` | String | The hyperlink associated with the URL. | -| `updated` | String | The timestamp indicating when the URL was last updated. | +| `title` | String | The title of the URL. | +| `description` | String | A brief description of the URL. | +| `updated` | String | The timestamp indicating when the URL was last updated. | This documentation outlines the structure and attributes of various objects involved in the reproducibility checklist project. From ebd9bab7210f2abffecaf5cad20426ab29b30310 Mon Sep 17 00:00:00 2001 From: AdiAkhileshSingh15 Date: Fri, 30 Aug 2024 19:37:24 +0530 Subject: [PATCH 12/43] data structure modification and method to list project languages --- app/components/ProjectNotes/ProjectNotes.js | 2 +- .../ReproChecklists/ChecklistItem.js | 57 ++++++++-- .../ReproChecklists/ReproChecklists.js | 105 ++++++++++-------- app/utils/asset.js | 2 +- 4 files changed, 109 insertions(+), 57 deletions(-) diff --git a/app/components/ProjectNotes/ProjectNotes.js b/app/components/ProjectNotes/ProjectNotes.js index 6df8f1f..7286e4f 100644 --- a/app/components/ProjectNotes/ProjectNotes.js +++ b/app/components/ProjectNotes/ProjectNotes.js @@ -157,7 +157,7 @@ function projectNotes(props) { }); } - const mappedPersonNotes = []; + let mappedPersonNotes = []; if (props.project.people) { props.project.people.forEach((p) => { if (p && p.notes) { diff --git a/app/components/ReproChecklists/ChecklistItem.js b/app/components/ReproChecklists/ChecklistItem.js index f19e1b8..1df4748 100644 --- a/app/components/ReproChecklists/ChecklistItem.js +++ b/app/components/ReproChecklists/ChecklistItem.js @@ -8,6 +8,7 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot const [notes, setNotes] = useState(item.userNotes || []); const [showImages, setShowImages] = useState(false); const [showURLs, setShowURLs] = useState(false); + const [allImages, setAllImages] = useState([]); const handleNoteUpdate = (note, text) => { if (note) { @@ -27,6 +28,24 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot } }; + useEffect(() => { + if (project && Array.isArray(project.assets)) { + const imageAssets = []; + const findImageAssets = (asset) => { + if (asset.type === 'image') { + imageAssets.push(asset.uri); + } + if (asset.children) { + asset.children.forEach(findImageAssets); + } + }; + + setAllImages(imageAssets); + + project.assets.forEach(findImageAssets); + } + }, [project]); + useEffect(() => { setNotes(item.userNotes || []); }, [item.userNotes]); @@ -37,19 +56,37 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot {item.statement}
+
+ {item.scanResult && + Object.keys(item.scanResult).map((key) => { + return ( +
+ {key} +
    + {item.scanResult[key].length ? ( + item.scanResult[key].map((answer) => ( +
  • {answer}
  • + )) + ) : ( +
  • No answers available
  • + )} +
+
+ )})} +
{ - if (project && Array.isArray(project.assets)) { - const imageAssets = []; - - const findImageAssets = (asset) => { - if (asset.type === 'image') { - imageAssets.push(asset); - } - if (asset.children) { - asset.children.forEach(findImageAssets); +function findAssetLanguagesAndDependencies(asset) { + if (asset.contentTypes.includes('code') && asset.type === 'file') { + const lastSep = asset.uri.lastIndexOf(path.sep); + const fileName = asset.uri.substring(lastSep + 1); + const ext = fileName.split('.').pop(); + if(ext){ + AssetsConfig.contentTypes.forEach((contentType) => { + if(contentType.extensions.includes(ext)) { + languages.add(contentType.name); } - }; + }); + } + } - project.assets.forEach(findImageAssets); + if(asset.children){ + asset.children.forEach(findAssetLanguagesAndDependencies); + } + + reproducibilityChecklist[2].answerList = {languages: Array.from(languages),dependencies: Array.from(dependencies) }; +} - const updatedItems = dummyChecklistItems.map(item => ({ - ...item, - attachedImages: imageAssets.map(asset => ({ - id: asset.id, - uri: asset.uri, - title: asset.title || 'Image', - description: asset.description || 'About Image', - updated: asset.updated || 'NA', - })), - })); +function ReproChecklists(props) { + const { project, reproChecklist, onUpdatedNote, onDeletedNote, onAddedNote } = props; + const [checklistItems, setChecklistItems] = useState(reproducibilityChecklist); - setChecklistItems(updatedItems); + useEffect(() => { + if (project && project.assets) { + findAssetLanguagesAndDependencies(project.assets); } }, [project]); @@ -145,7 +152,10 @@ ReproChecklists.propTypes = { id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, type: PropTypes.string.isRequired, - answer: PropTypes.string.isRequired, + bool: PropTypes.bool.isRequired, + answerList: PropTypes.objectOf( + PropTypes.arrayOf(PropTypes.string) + ), userNotes: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, @@ -177,11 +187,12 @@ ReproChecklists.propTypes = { id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, type: PropTypes.string.isRequired, - answer: PropTypes.string.isRequired, - updated: PropTypes.string.isRequired, + bool: PropTypes.bool.isRequired, + answerList: PropTypes.objectOf( + PropTypes.arrayOf(PropTypes.string) + ), }) ), - updated: PropTypes.string.isRequired, }) ).isRequired, onUpdatedNote: PropTypes.func.isRequired, @@ -190,7 +201,7 @@ ReproChecklists.propTypes = { }; ReproChecklists.defaultProps = { - reproChecklist: dummyChecklistItems, + reproChecklist: reproducibilityChecklist, }; export default ReproChecklists; diff --git a/app/utils/asset.js b/app/utils/asset.js index afa3256..7a8bd49 100644 --- a/app/utils/asset.js +++ b/app/utils/asset.js @@ -1,5 +1,5 @@ /* eslint-disable no-underscore-dangle */ -import path from 'path'; +const path = require('path'); import last from 'lodash/last'; import constants from '../constants/constants'; From 78d3aef6fcb8c387c56caa5aaeeb03c515d48f2e Mon Sep 17 00:00:00 2001 From: AdiAkhileshSingh15 Date: Fri, 30 Aug 2024 20:05:00 +0530 Subject: [PATCH 13/43] smol fix --- .../ReproChecklists/ReproChecklists.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/components/ReproChecklists/ReproChecklists.js b/app/components/ReproChecklists/ReproChecklists.js index babdb0a..ff62dba 100644 --- a/app/components/ReproChecklists/ReproChecklists.js +++ b/app/components/ReproChecklists/ReproChecklists.js @@ -10,8 +10,8 @@ const reproducibilityChecklist = [ id: '1', statement: 'First checklist item statement. Now, I am just trying to make this statement a bit longer to see how it looks in the UI, without making any sense.', type: 'Type1', - bool: true, - answerList: {}, + answer: true, + scanResult: {}, userNotes: [ { id: 'note1', @@ -66,8 +66,8 @@ const reproducibilityChecklist = [ id: '2', statement: 'Second checklist item statement', type: 'Type2', - bool: false, - answerList: {}, + answer: false, + scanResult: {}, userNotes: [ { id: 'note2', @@ -84,8 +84,8 @@ const reproducibilityChecklist = [ id: '3', statement: 'List of all the languages used and the dependencies of the project', type: 'List', - bool: true, - answerList: {}, + answer: true, + scanResult: {}, userNotes: [], attachedImages: [], attachedURLs: [], @@ -152,8 +152,8 @@ ReproChecklists.propTypes = { id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, type: PropTypes.string.isRequired, - bool: PropTypes.bool.isRequired, - answerList: PropTypes.objectOf( + answer: PropTypes.bool.isRequired, + scanResult: PropTypes.objectOf( PropTypes.arrayOf(PropTypes.string) ), userNotes: PropTypes.arrayOf( @@ -187,8 +187,8 @@ ReproChecklists.propTypes = { id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, type: PropTypes.string.isRequired, - bool: PropTypes.bool.isRequired, - answerList: PropTypes.objectOf( + answer: PropTypes.bool.isRequired, + scanResult: PropTypes.objectOf( PropTypes.arrayOf(PropTypes.string) ), }) From aecf359f7b0a2372c08af43eb8841d65d260431a Mon Sep 17 00:00:00 2001 From: AdiAkhileshSingh15 Date: Sun, 22 Sep 2024 23:07:45 +0530 Subject: [PATCH 14/43] implements feature to add images and urls --- app/components/Project/Project.js | 4 +- .../ReproChecklists/ChecklistItem.css | 126 ++++++++- .../ReproChecklists/ChecklistItem.js | 258 +++++++++++++----- .../ReproChecklists/Modal/Modal.css | 52 ++++ app/components/ReproChecklists/Modal/Modal.js | 45 +++ .../ReproChecklists/ReproChecklists.js | 106 ++++--- app/constants/assets-config.js | 7 +- app/constants/constants.js | 1 + 8 files changed, 465 insertions(+), 134 deletions(-) create mode 100644 app/components/ReproChecklists/Modal/Modal.css create mode 100644 app/components/ReproChecklists/Modal/Modal.js diff --git a/app/components/Project/Project.js b/app/components/Project/Project.js index 7a11409..6968312 100644 --- a/app/components/Project/Project.js +++ b/app/components/Project/Project.js @@ -682,7 +682,7 @@ class Project extends Component { /> ) : null; - const Checklists = { {projectLog} - {Checklists} + {checklists} ); diff --git a/app/components/ReproChecklists/ChecklistItem.css b/app/components/ReproChecklists/ChecklistItem.css index a1ce89b..0daa9f9 100644 --- a/app/components/ReproChecklists/ChecklistItem.css +++ b/app/components/ReproChecklists/ChecklistItem.css @@ -9,14 +9,33 @@ .header { display:flex; - align-items: center; + justify-content: center; } +.statement { + font-size: 18px; + font-weight: bold; + margin-right: 5px; + margin-bottom: 10px; +} .buttonContainer { + display: flex; margin-left: auto; margin-right: 0; } +.scanResult { + font-size: medium; + font-family:'Courier New', Courier, monospace; +} + +.scanKey { +} + +.scanList { + margin-top: 4px; +} + .details { padding-left: 20px; margin-top: 10px; @@ -91,6 +110,80 @@ max-height: 500px; } +.subChecksContent { + overflow: hidden; + max-height: 0; + transition: max-height 0.3s ease; +} + +.subChecksContent.show { + max-height: 1000px; +} + +.image { + text-wrap: wrap; +} +.url { + background-color:#f2ebff; + border:1px solid rebeccapurple; + padding: 7px; + padding-right: 0px; + border-radius: 5px; + text-wrap: wrap; + word-wrap: break-word; +} + +.selectedImage { + display: block; + width: 100%; + max-width: 300px; + margin-bottom: 10px; +} + +.hyperlink { + display: flex; + justify-content: space-evenly; + margin-bottom: 10px; +} + +.urlHeader { + display: flex; + justify-content: space-between; + align-items: center; +} + +.btn { + background: none; + padding-right: 0px; + margin-right: 0px; +} + +.doneButton { + background-color: transparent; + color: #2bc621; + border-radius: 5px; + cursor: pointer; + margin-right: 0px; + transition: background-color 0.3s ease; +} + +.doneButton:hover { + background-color: #dfd2f7; +} + +.copyButton { + background-color: transparent; + color: rebeccapurple; + border-radius: 5px; + cursor: pointer; + margin-right: 0px; + transition: background-color 0.3s ease; +} + +.copyButton:hover { + background-color: #dfd2f7; +} + .urls a { text-decoration: none; color: #007bff; @@ -141,6 +234,33 @@ opacity: 0.9; } +.addImagesButton, .addUrlsButton { + background-color: #aa94d1; + color: #fff; + cursor: pointer; + padding: 5px 10px; + margin-top: 5px; + border-radius: 4px; + font-size: 16px; + transition: background-color 0.3s ease; +} + +.addImagesButton:hover, .addUrlsButton:hover { + background-color: #8b6fb3; +} + +.addIcon { + height: 28px; + width: 28px; + color: rebeccapurple; + cursor: pointer; + transition: color 0.5s ease; +} + +.addIcon:hover { + opacity: 0.8; +} + .headerWithButton { display: flex; align-items: center; @@ -177,7 +297,7 @@ max-height: 500px; } -.imageTitle, -.urlTitle { +.urlText, .imageText { font-size: 16px; + font-weight: 600; } diff --git a/app/components/ReproChecklists/ChecklistItem.js b/app/components/ReproChecklists/ChecklistItem.js index 1df4748..71318de 100644 --- a/app/components/ReproChecklists/ChecklistItem.js +++ b/app/components/ReproChecklists/ChecklistItem.js @@ -2,13 +2,46 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import styles from './ChecklistItem.css'; import NoteEditor from '../NoteEditor/NoteEditor'; +import { AddBox, ContentCopy , Done} from '@mui/icons-material'; +import Modal from './Modal/Modal'; -function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNote }) { - const [answer, setAnswer] = useState(item.answer); - const [notes, setNotes] = useState(item.userNotes || []); +function ChecklistItem(props) { + const { item, project, imageAssets, onUpdatedNote, onDeletedNote, onAddedNote, onItemUpdate } = props; + const [checklistItem, setChecklistItem] = useState(item); + const [addImages, setAddImages] = useState(false); + const [addURL, setAddURL] = useState(false); const [showImages, setShowImages] = useState(false); const [showURLs, setShowURLs] = useState(false); - const [allImages, setAllImages] = useState([]); + const [showSubChecks, setShowSubChecks] = useState(false); + const [imageModal, setImageModal] = useState({isOpen: false, image: ''}); + const [copiedUrlId, setCopiedUrlId] = useState(null); + + const handleSubmitImage = (e) => { + e.preventDefault(); + const img ={id: checklistItem.attachedImages.length + 1, uri: imageModal.image, title: e.target.title.value, description: e.target.description.value}; + const updatedItem = { ...item, attachedImages: [...checklistItem.attachedImages, img] }; + onItemUpdate(updatedItem); + setChecklistItem(updatedItem); + setImageModal({isOpen: false, image: ''}); + } + + const handleSubmitUrl = (e) => { + e.preventDefault(); + const url = {id: checklistItem.attachedURLs.length + 1, hyperlink: e.target.hyperlink.value, title: e.target.title.value, description: e.target.description.value}; + const updatedItem = { ...item, attachedURLs: [...checklistItem.attachedURLs, url] }; + onItemUpdate(updatedItem); + setChecklistItem(updatedItem); + setAddURL(false); + } + + const handleCopy = (urlId, hyperlink) => { + navigator.clipboard.writeText(hyperlink).then(() => { + setCopiedUrlId(urlId); + setTimeout(() => { + setCopiedUrlId(null); + }, 3000); + }); + }; const handleNoteUpdate = (note, text) => { if (note) { @@ -28,56 +61,53 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot } }; - useEffect(() => { - if (project && Array.isArray(project.assets)) { - const imageAssets = []; - const findImageAssets = (asset) => { - if (asset.type === 'image') { - imageAssets.push(asset.uri); - } - if (asset.children) { - asset.children.forEach(findImageAssets); - } - }; + let imageComponent = null; + let urlComponent = null; - setAllImages(imageAssets); - - project.assets.forEach(findImageAssets); - } - }, [project]); - - useEffect(() => { - setNotes(item.userNotes || []); - }, [item.userNotes]); + imageComponent = selected; + urlComponent = ( +
+ + +
+ ) return (
- {item.statement} + {checklistItem.statement}
- {item.scanResult && - Object.keys(item.scanResult).map((key) => { + {checklistItem.scanResult && + Object.keys(checklistItem.scanResult).map((key) => { return (
- {key} -
    - {item.scanResult[key].length ? ( - item.scanResult[key].map((answer) => ( + {key} +
      + {checklistItem.scanResult[key].length ? ( + checklistItem.scanResult[key].map((answer) => (
    • {answer}
    • )) ) : ( @@ -89,11 +119,11 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot
- {item.attachedImages.length > 0 && ( + {checklistItem.attachedImages.length > 0 && (

Attached Images:

@@ -103,19 +133,42 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot
    - {item.attachedImages.map((image) => ( -
  • - {image.title} - attached + {checklistItem.attachedImages.map((image) => ( +
  • + {image.title} + attached

    {image.description}

    - {image.updated}
  • ))}
)} - {item.attachedURLs.length > 0 && ( + {imageAssets.length > 0 && ( +
+ +
+
    + {imageAssets.map((image) => ( +
  • + setImageModal({isOpen: true, image: image})}/> + project image +
  • + ))} +
+
+
+ )} + setImageModal({isOpen: false, image: ''})} + onSubmit={handleSubmitImage} + title="Image" + component={imageComponent} + /> + {checklistItem.attachedURLs.length > 0 && (

Attached URLs:

@@ -125,33 +178,102 @@ function ChecklistItem({ item, project, onUpdatedNote, onDeletedNote, onAddedNot
    - {item.attachedURLs.map((url) => ( -
  • - {url.title} - - {url.hyperlink} - -

    {url.description}

    - {url.updated} -
  • + {checklistItem.attachedURLs.map((url) => ( +
    +
    +
  • +
    + {url.title} + {copiedUrlId === url.id ? ( + + ) : ( + + )} +
    + + {url.hyperlink} + +

    {url.description}

    +
  • +
    +
    +
    ))}
)} - {item.subChecklists.length > 0 && ( +
+ +
+ setAddURL(false)} + onSubmit={handleSubmitUrl} + title="URL" + component={urlComponent} + /> + {checklistItem.subChecklists.length > 0 && (
-

Sub-Checklists:

- {item.subChecklists.map((subItem) => ( - - ))} +
+

Sub-Checklists:

+ +
+
+
    + {checklistItem.subChecklists.map((subCheck) => ( +
  • +
    + {subCheck.statement} +
    + + +
    +
    +
  • + ))} +
+
)}
@@ -163,7 +285,6 @@ ChecklistItem.propTypes = { item: PropTypes.shape({ id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, answer: PropTypes.bool.isRequired, scanResult: PropTypes.objectOf( PropTypes.arrayOf(PropTypes.string) @@ -182,7 +303,6 @@ ChecklistItem.propTypes = { uri: PropTypes.string.isRequired, title: PropTypes.string.isRequired, description: PropTypes.string.isRequired, - updated: PropTypes.string.isRequired, }) ), attachedURLs: PropTypes.arrayOf( @@ -191,22 +311,18 @@ ChecklistItem.propTypes = { hyperlink: PropTypes.string.isRequired, title: PropTypes.string.isRequired, description: PropTypes.string.isRequired, - updated: PropTypes.string.isRequired, }) ), subChecklists: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, statement: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, answer: PropTypes.bool.isRequired, - scanResult: PropTypes.objectOf( - PropTypes.arrayOf(PropTypes.string) - ), }) ), }).isRequired, project: PropTypes.object.isRequired, + imageAssets: PropTypes.arrayOf(PropTypes.string), onUpdatedNote: PropTypes.func.isRequired, onDeletedNote: PropTypes.func.isRequired, onAddedNote: PropTypes.func.isRequired, diff --git a/app/components/ReproChecklists/Modal/Modal.css b/app/components/ReproChecklists/Modal/Modal.css new file mode 100644 index 0000000..9af4cfd --- /dev/null +++ b/app/components/ReproChecklists/Modal/Modal.css @@ -0,0 +1,52 @@ +.modal { + display: flex; + justify-content: center; + align-items: center; +} + +.form { + background-color: #fff; + padding: 20px; + padding-top: 0px; + border-radius: 8px; + max-width: 300px; + width: 100%; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.header { + display:flex; + justify-content: center; +} + +.title { + display: flex; + justify-content: space-evenly; +} +.description { + display: flex; + justify-content: space-evenly; + height: 50px; + margin-top: 10px; +} + +.submit { + display: flex; + justify-content: end; + margin-top: 5px; +} + +.submitButton { + background-color: #ccc; + color: #000; + border: none; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + font-size: 15px; + transition: background-color 0.3s ease; +} + +.submitButton:hover { + background-color: #e5e5e5; +} diff --git a/app/components/ReproChecklists/Modal/Modal.js b/app/components/ReproChecklists/Modal/Modal.js new file mode 100644 index 0000000..d6ee30a --- /dev/null +++ b/app/components/ReproChecklists/Modal/Modal.js @@ -0,0 +1,45 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './Modal.css'; +import { Modal } from '@mui/material'; + +function AttachModal(props) { + const { isOpen, onClose, onSubmit, title, component } = props; + return ( + +
+
+

Attach {title}

+
+ {component} +
+ + +
+
+ +