From db93de281dc45049d8e4bb9ecdfea18b66270f86 Mon Sep 17 00:00:00 2001 From: Sam Park Date: Mon, 5 Aug 2024 13:56:06 -0400 Subject: [PATCH 01/49] deleting view that is selected now unselects the view selector --- web/src/components/modals/add-view-options.tsx | 5 ++++- web/src/components/project/view-selector.tsx | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/web/src/components/modals/add-view-options.tsx b/web/src/components/modals/add-view-options.tsx index ba6fee3c..c00ab6d6 100644 --- a/web/src/components/modals/add-view-options.tsx +++ b/web/src/components/modals/add-view-options.tsx @@ -15,6 +15,7 @@ type Props = { show: boolean; onHide: () => void; filteredSamples: string[]; + deleteView: (deletedView: string) => void; }; type FormValues = { @@ -30,7 +31,7 @@ type ViewOption = { }; export const ViewOptionsModal = (props: Props) => { - const { show, onHide, filteredSamples } = props; + const { show, onHide, filteredSamples, deleteView } = props; const { namespace, projectName, tag } = useProjectPage(); @@ -66,7 +67,9 @@ export const ViewOptionsModal = (props: Props) => { return; } viewMutations.removeViewMutation.mutate(selectedViewDelete.value); + console.log(selectedViewDelete) setSelectedViewDelete(null); + deleteView(selectedViewDelete.value) }; const onSubmit = () => { diff --git a/web/src/components/project/view-selector.tsx b/web/src/components/project/view-selector.tsx index d9008b7a..a7fe6631 100644 --- a/web/src/components/project/view-selector.tsx +++ b/web/src/components/project/view-selector.tsx @@ -41,6 +41,15 @@ export const ViewSelector = (props: ViewSelectorProps) => { const { user } = useSession(); const { data: projectInfo } = useProjectAnnotation(namespace, projectName, tag); + const deleteView = (deletedView: string) => { + if (selectRef.current.getValue()[0].value === deletedView) { + setView(undefined); + searchParams.delete('view'); + setSearchParams(searchParams); + selectRef.current.clearValue(); + }; + }; + const userHasOwnership = user && projectInfo && canEdit(user, projectInfo); const selectorRadius = userHasOwnership ? '0 .25em .25em 0' : '.25em'; @@ -121,6 +130,7 @@ export const ViewSelector = (props: ViewSelectorProps) => { show={showViewOptionsModal} onHide={() => setShowViewOptionsModal(false)} filteredSamples={filteredSamples} + deleteView={deleteView} /> ); From 057803ac72e07547f15f0751e8c730919a7ab404 Mon Sep 17 00:00:00 2001 From: Sam Park Date: Mon, 5 Aug 2024 14:33:29 -0400 Subject: [PATCH 02/49] rounded corner in schema select dropdown --- .../forms/components/schemas-databio-dropdown.tsx | 6 ++++++ web/src/components/modals/add-view-options.tsx | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/web/src/components/forms/components/schemas-databio-dropdown.tsx b/web/src/components/forms/components/schemas-databio-dropdown.tsx index 64bc24c7..ea8553bc 100644 --- a/web/src/components/forms/components/schemas-databio-dropdown.tsx +++ b/web/src/components/forms/components/schemas-databio-dropdown.tsx @@ -32,6 +32,12 @@ const SchemaDropdown: FC = ({ value, onChange, showDownload = true }) => isClearable menuPlacement="top" className="w-100" + styles={{ + control: (provided) => ({ + ...provided, + borderRadius: '.33333em', + }) + }} /> {showDownload && ( { return; } viewMutations.removeViewMutation.mutate(selectedViewDelete.value); - console.log(selectedViewDelete) setSelectedViewDelete(null); deleteView(selectedViewDelete.value) }; From 7905ce6304d100bf7cb494ae13b94975ce008dbd Mon Sep 17 00:00:00 2001 From: Sam Park Date: Mon, 5 Aug 2024 17:07:55 -0400 Subject: [PATCH 03/49] added modal for no schema yellow button and prevented clearing schemas from dropdown because it doesn --- .../components/schemas-databio-dropdown.tsx | 2 +- .../components/modals/validation-result.tsx | 72 ++++++++++++------- .../project-validation-and-edit-buttons.tsx | 36 ++-------- .../project/validation/validation-result.tsx | 16 +++++ 4 files changed, 72 insertions(+), 54 deletions(-) diff --git a/web/src/components/forms/components/schemas-databio-dropdown.tsx b/web/src/components/forms/components/schemas-databio-dropdown.tsx index ea8553bc..027e9e66 100644 --- a/web/src/components/forms/components/schemas-databio-dropdown.tsx +++ b/web/src/components/forms/components/schemas-databio-dropdown.tsx @@ -29,7 +29,7 @@ const SchemaDropdown: FC = ({ value, onChange, showDownload = true }) => onChange(newValue?.value || ''); }} placeholder={isLoading ? 'Fetching schemas...' : 'Assign a schema...'} - isClearable + // isClearable menuPlacement="top" className="w-100" styles={{ diff --git a/web/src/components/modals/validation-result.tsx b/web/src/components/modals/validation-result.tsx index 681cb5ed..10f5494d 100644 --- a/web/src/components/modals/validation-result.tsx +++ b/web/src/components/modals/validation-result.tsx @@ -19,7 +19,7 @@ type FormProps = { }; export const ValidationResultModal = (props: Props) => { - const { show, onHide, validationResult } = props; + const { show, onHide, validationResult, currentSchema } = props; const { namespace, projectName, tag } = useProjectPage(); @@ -33,9 +33,15 @@ export const ValidationResultModal = (props: Props) => { const newSchema = updateForm.watch('schema'); const handleSubmit = () => { - submit({ - newSchema, - }); + if (newSchema === '') { + submit({ + newSchema: null, + }); + } else { + submit({ + newSchema: newSchema, + }); + } }; return ( @@ -49,33 +55,51 @@ export const ValidationResultModal = (props: Props) => { >

- {validationResult?.valid ? ( - - - Validation Passed - + {currentSchema ? ( + <> + {validationResult?.valid ? ( + + + Validation Passed + + ) : ( + + + Validation Failed + + )} + ) : ( - - - Validation Failed + + Select a Schema )}

- {validationResult?.valid ? ( -

Your PEP is valid against the schema.

- ) : ( - -

You PEP is invalid against the schema.

-

Validation result:

-
-              {JSON.stringify(validationResult, null, 2)}
-            
-
+ {currentSchema ? ( + <> + {validationResult?.valid ? ( +

Your PEP is valid against the schema.

+ ) : ( + +

You PEP is invalid against the schema.

+

Validation result:

+
+                {JSON.stringify(validationResult, null, 2)}
+              
+
+ )} + + ) : ( null )} -
- + + + {currentSchema ? ( + + ) : ( + + )}
- {projectInfo?.pep_schema ? ( - - ) : ( -
- <> - - As you edit your project below, it will be validated against the schema currently selected for - it. - - } - delay={{ show: 250, hide: 500 }} - trigger={['hover']} - > -
- - No schema -
-
- -
- )} -
+
+ ) : ( + + )} + Date: Tue, 6 Aug 2024 10:54:01 -0400 Subject: [PATCH 04/49] missed closing div --- .../components/project/project-validation-and-edit-buttons.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/components/project/project-validation-and-edit-buttons.tsx b/web/src/components/project/project-validation-and-edit-buttons.tsx index 924c03a7..beeb5ece 100644 --- a/web/src/components/project/project-validation-and-edit-buttons.tsx +++ b/web/src/components/project/project-validation-and-edit-buttons.tsx @@ -49,6 +49,7 @@ export const ProjectValidationAndEditButtons = (props: ProjectValidationAndEditB isValidating={projectValidationQuery.isLoading} validationResult={validationResult} /> +
+ + + + + ); +}; diff --git a/web/src/components/project/project-header.tsx b/web/src/components/project/project-header.tsx index b052e4b1..5a94bdb6 100644 --- a/web/src/components/project/project-header.tsx +++ b/web/src/components/project/project-header.tsx @@ -2,10 +2,18 @@ import { ProjectInfoFooter } from './project-info-footer'; import { ProjectDescription } from './project-page-description'; import { ProjectHeaderBar } from './project-page-header-bar'; -export const ProjectHeader = () => { + +type Props = { + sampleTable: ReturnType['data']; + sampleTableIndex: string; +}; + +export const ProjectHeader = (props: Props) => { + const { sampleTable, sampleTableIndex } = props; + return (
- +
diff --git a/web/src/components/project/project-page-header-bar.tsx b/web/src/components/project/project-page-header-bar.tsx index 020c0b65..ed2b381c 100644 --- a/web/src/components/project/project-page-header-bar.tsx +++ b/web/src/components/project/project-page-header-bar.tsx @@ -18,10 +18,18 @@ import { canEdit } from '../../utils/permissions'; import { downloadZip } from '../../utils/project'; import { ProjectHistoryModal } from '../modals/project-history'; import { ProjectHeaderBarPlaceholder } from './placeholders/project-header-bar-placeholder'; +import { StandardizeMetadataModal } from '../modals/standardize-metadata'; -type ProjectPageHeaderBarProps = {}; -export const ProjectHeaderBar = (props: ProjectPageHeaderBarProps) => { + +type Props = { + sampleTable: ReturnType['data']; + sampleTableIndex: string; +}; + +export const ProjectHeaderBar = (props: Props) => { + const { sampleTable, sampleTableIndex } = props; + const { user, login, jwt } = useSession(); const [searchParams] = useSearchParams(); @@ -45,6 +53,7 @@ export const ProjectHeaderBar = (props: ProjectPageHeaderBarProps) => { const [showEditMetaMetadataModal, setShowEditMetaMetadataModal] = useState(false); const [showAddToPOPModal, setShowAddToPOPModal] = useState(false); const [showProjectHistoryModal, setShowProjectHistoryModal] = useState(false); + const [showStandardizeMetadataModal, setShowStandardizeMetadataModal] = useState(false); // queries const projectAnnotationQuery = useProjectAnnotation(namespace, projectName, tag); @@ -179,13 +188,17 @@ export const ProjectHeaderBar = (props: ProjectPageHeaderBarProps) => { {user && projectInfo && canEdit(user, projectInfo) && ( + setShowEditMetaMetadataModal(true)}> + + Edit + setShowProjectHistoryModal(true)}> History - setShowEditMetaMetadataModal(true)}> - - Edit + setShowStandardizeMetadataModal(true)}> + + Standardize setShowDeletePEPModal(true)}> @@ -274,9 +287,7 @@ export const ProjectHeaderBar = (props: ProjectPageHeaderBarProps) => { /> { - setShowAddToPOPModal(false); - }} + onHide={() => setShowAddToPOPModal(false)} namespace={namespace!} project={projectName} tag={tag} @@ -288,6 +299,15 @@ export const ProjectHeaderBar = (props: ProjectPageHeaderBarProps) => { project={projectName} tag={tag} /> + setShowStandardizeMetadataModal(false)} + namespace={namespace} + project={projectName} + tag={tag} + sampleTable={sampleTable} + sampleTableIndex={sampleTableIndex} + />
); }; diff --git a/web/src/globals.css b/web/src/globals.css index d28464d2..a3b90159 100644 --- a/web/src/globals.css +++ b/web/src/globals.css @@ -688,3 +688,14 @@ body { .btn-dark.glow-button { --box-shadow-color: #343a4080; } + +.btn-group-vertical .btn-check:checked + .btn-outline-secondary{ + box-shadow: inset 0 0 10px 2.5px #28a74524 !important; + outline: none; + border: 1px solid #28a745; + + background-color: #28a74516; + color: #28a745; +} + + diff --git a/web/src/pages/Project.tsx b/web/src/pages/Project.tsx index 98a68fc3..c9138d74 100644 --- a/web/src/pages/Project.tsx +++ b/web/src/pages/Project.tsx @@ -115,7 +115,7 @@ export const ProjectPage = () => { )} - + {projectInfo?.pop && !forceTraditionalInterface ? ( ) : ( From 9ecb06ee5e59e13c2457462115d8671acf56675a Mon Sep 17 00:00:00 2001 From: Sam Park Date: Tue, 13 Aug 2024 11:43:47 -0400 Subject: [PATCH 06/49] standardizer ui kinda works --- .../modals/standardize-metadata.tsx | 94 ++++++++++++++----- .../components/project/project-interface.tsx | 20 ++++ .../project/project-page-header-bar.tsx | 14 +-- web/src/globals.css | 2 +- .../hooks/stores/useStandardizeModalStore.ts | 11 +++ 5 files changed, 108 insertions(+), 33 deletions(-) create mode 100644 web/src/hooks/stores/useStandardizeModalStore.ts diff --git a/web/src/components/modals/standardize-metadata.tsx b/web/src/components/modals/standardize-metadata.tsx index 41259a07..967232dd 100644 --- a/web/src/components/modals/standardize-metadata.tsx +++ b/web/src/components/modals/standardize-metadata.tsx @@ -14,18 +14,23 @@ type Props = { onHide: () => void; sampleTable: ReturnType['data']; sampleTableIndex: string; + newSamples: any; + setNewSamples: (samples: any[][]) => void; }; export const StandardizeMetadataModal = (props: Props) => { - const { namespace, project, tag, show, onHide, sampleTable, sampleTableIndex } = props; + const { namespace, project, tag, show, onHide, sampleTable, sampleTableIndex, newSamples, setNewSamples } = props; + + // const tabDataRaw = sampleListToArrays(sampleTable?.items || []) + const tabDataRaw = newSamples; - const tabDataRaw = sampleListToArrays(sampleTable?.items || []) const tabData = tabDataRaw[0].map((_, colIndex) => tabDataRaw.map(row => row[colIndex])).reduce((obj, row) => { const [key, ...values] = row; obj[key] = values; return obj; }, {}); + // console.log(tabDataRaw) // console.log(tabData) // console.log(sampleTableIndex) @@ -38,25 +43,13 @@ export const StandardizeMetadataModal = (props: Props) => { return `${(value * 100).toFixed(2)}%`; }; - const [selectedValues, setSelectedValues] = useState({}); - const [hasDuplicates, setHasDuplicates] = useState(false); - const handleRadioChange = (key, value) => { setSelectedValues(prev => { - let newValues; - if (value === null) { - // Create a new object without the specified key - const { [key]: _, ...rest } = prev; - newValues = rest; - } else { - // Add or update the key-value pair - newValues = { - ...prev, - [key]: value - }; - } + const newValues = { + ...prev, + [key]: value === null ? key : value + }; - // Check for duplicates setHasDuplicates(checkForDuplicates(newValues)); return newValues; @@ -82,6 +75,47 @@ export const StandardizeMetadataModal = (props: Props) => { return false; // No duplicates found }; + const getDefaultSelections = () => { + const defaultSelections = {}; + Object.keys(data).forEach(key => { + if (data[key]['Not Predictable'] === 0) { + defaultSelections[key] = key; // Use the key itself as the default value + } else { + const options = Object.entries(data[key]); + const [bestOption] = options.reduce((best, current) => + current[1] > best[1] && current[0] !== 'Not Predictable' ? current : best + ); + defaultSelections[key] = bestOption; + } + }); + return defaultSelections; + }; + + type TabDataRow = string[]; + type TabData = TabDataRow[]; + type SelectedValues = Record; + + const updateTabDataRaw = (tabDataRaw: TabData, selectedValues: SelectedValues): TabData => { + if (tabDataRaw.length === 0) return tabDataRaw; + + // Create a new array to avoid mutating the original + const updatedTabDataRaw: TabData = [tabDataRaw[0].slice(), ...tabDataRaw.slice(1)]; + + // Update only the column names (first row) based on selectedValues + Object.entries(selectedValues).forEach(([key, value]) => { + const columnIndex = updatedTabDataRaw[0].indexOf(key); + if (columnIndex !== -1 && key !== value) { + updatedTabDataRaw[0][columnIndex] = value; + } + }); + + return updatedTabDataRaw; + }; + + + const [selectedValues, setSelectedValues] = useState(getDefaultSelections()); + const [hasDuplicates, setHasDuplicates] = useState(false); + return ( @@ -131,7 +165,7 @@ export const StandardizeMetadataModal = (props: Props) => { name={key} id={`${key}-suggested-${subKey}`} value={subKey} - defaultChecked={(value !== 0) && (index === 0)} + defaultChecked={selectedValues[key] === subKey} disabled={data[key]['Not Predictable'] === 0} onChange={() => handleRadioChange(key, subKey)} /> @@ -146,8 +180,8 @@ export const StandardizeMetadataModal = (props: Props) => { type="radio" name={key} id={`${key}-original`} - value="original" - defaultChecked={data[key]['Not Predictable'] === 0} + value={key} + defaultChecked={selectedValues[key] === key} // Check if the selected value is the same as the key onChange={() => handleRadioChange(key, null)} />
-
-
-

{key}

- - {tabData[key] && ( -

[{tabData[key].slice(0, 3).join(', ')}]

- )} +
+
+ +
+ [item]) || []) + ]} + colHeaders={false} + rowHeaders={true} + width='100%' + height='90%' + colWidths="100%" // Set all columns to 100% width + stretchH="all" // Stretch all columns + autoColumnSize={false} // Disable auto column sizing + columns={[ + { + data: 0, + type: typeof tabData[key] === 'number' ? 'numeric' : 'text', + renderer: function(instance, td, row, col, prop, value, cellProperties) { + Handsontable.renderers.TextRenderer.apply(this, arguments); + if (row === 0) { + td.style.fontWeight = 'bold'; + } + } + } + ]} + licenseKey="non-commercial-and-evaluation" + /> +
-
+
{Object.entries(data[key]).map(([subKey, value], index, array) => ( @@ -169,7 +199,7 @@ export const StandardizeMetadataModal = (props: Props) => { disabled={data[key]['Not Predictable'] === 0} onChange={() => handleRadioChange(key, subKey)} /> - @@ -184,7 +214,7 @@ export const StandardizeMetadataModal = (props: Props) => { defaultChecked={selectedValues[key] === key} // Check if the selected value is the same as the key onChange={() => handleRadioChange(key, null)} /> -
@@ -198,7 +228,7 @@ export const StandardizeMetadataModal = (props: Props) => { - {hasDuplicates && ( + {whereDuplicates !== null && (
Warning: ensure no duplicates between original and predicted columns have been selected.
@@ -215,7 +245,7 @@ export const StandardizeMetadataModal = (props: Props) => {
-
+
+ + +
+
+
Standardizer Schema
+
+
+ + ({ + ...provided, + borderRadius: '.333333em', // Left radii set to 0, right radii kept at 4px + }), + }} + options={ + [ + { value: 'ENCODE', label: 'ENCODE' }, + { value: 'Fairtracks', label: 'Fairtracks' }, + { value: 'vanilla', label: 'Vanilla' } + ] + } + /> +
+
+ +
+
+
+ +
+
Original Column
-
Predicted Standardized Column
+
Predicted Column Header
+ {Object.keys(data).map((key, index) => (
-
+
+
-
+
[item]) || []) - ]} + data={prepareHandsontableData(key, selectedValues, tabData)} colHeaders={false} rowHeaders={true} width='100%' - height='90%' - colWidths="100%" // Set all columns to 100% width - stretchH="all" // Stretch all columns - autoColumnSize={false} // Disable auto column sizing + height='100%' + colWidths="100%" + stretchH="all" + autoColumnSize={false} columns={[ { data: 0, @@ -175,48 +223,55 @@ export const StandardizeMetadataModal = (props: Props) => { Handsontable.renderers.TextRenderer.apply(this, arguments); if (row === 0) { td.style.fontWeight = 'bold'; + if (whereDuplicates?.includes(index)) { + td.style.color= 'red'; + } } + } } ]} licenseKey="non-commercial-and-evaluation" + className='custom-handsontable' />
-
-
- {Object.entries(data[key]).map(([subKey, value], index, array) => ( - - - handleRadioChange(key, subKey)} - /> - - - ))} - - handleRadioChange(key, null)} - /> - +
+
+
+ handleRadioChange(key, null)} + /> + + + {Object.entries(data[key]).map(([subKey, value], index, array) => ( + + + handleRadioChange(key, subKey)} + /> + + + ))} +

@@ -230,7 +285,7 @@ export const StandardizeMetadataModal = (props: Props) => { {whereDuplicates !== null && (
- Warning: ensure no duplicates between original and predicted columns have been selected. + Warning: ensure no duplicate column names have been selected.
)} diff --git a/web/src/globals.css b/web/src/globals.css index c2902bd9..907300f0 100644 --- a/web/src/globals.css +++ b/web/src/globals.css @@ -699,3 +699,39 @@ body { } +.custom-handsontable .handsontable { +/* border-radius: 8px;*/ + overflow: hidden; +} + +.custom-handsontable .handsontable th, +.custom-handsontable .handsontable td { +/* border-color: #ccc;*/ +} + +.custom-handsontable .handsontable th:first-child, +.custom-handsontable .handsontable td:first-child { + border-left: none !important; +} + +.custom-handsontable .handsontable th:last-child, +.custom-handsontable .handsontable td:last-child { + border-right: none !important; +} + +.custom-handsontable .handsontable tr:first-child th, +.custom-handsontable .handsontable tr:first-child td { + border-top: none !important; +} + +.custom-handsontable .handsontable tr:last-child th, +.custom-handsontable .handsontable tr:last-child td { + border-bottom: none !important; +} + +.btn-group-vertical { + bottom: -1px; +} + + + From 862f891e642fa450f96cbfb797c7233005b77de8 Mon Sep 17 00:00:00 2001 From: Sam Park Date: Fri, 16 Aug 2024 12:00:07 -0400 Subject: [PATCH 09/49] progress on standardizer api, trouble using hook to refetch it --- pephub/routers/api/v1/project.py | 37 +++ web/src/api/project.ts | 21 ++ .../modals/standardize-metadata.tsx | 250 ++++++++++-------- web/src/globals.css | 5 - web/src/hooks/queries/useStandardize.ts | 14 + 5 files changed, 213 insertions(+), 114 deletions(-) create mode 100644 web/src/hooks/queries/useStandardize.ts diff --git a/pephub/routers/api/v1/project.py b/pephub/routers/api/v1/project.py index 01718e64..847510fd 100644 --- a/pephub/routers/api/v1/project.py +++ b/pephub/routers/api/v1/project.py @@ -55,6 +55,7 @@ ConfigResponseModel, ) from .helpers import verify_updated_project +from attribute_standardizer.attr_standardizer_class import AttrStandardizer _LOGGER = logging.getLogger(__name__) @@ -1138,3 +1139,39 @@ def delete_full_history( status_code=400, detail="Could not delete history. Server error.", ) + + +@project.get( + "/standardize", + summary="Standardize PEP metadata column headers", +) +async def get_standardized_cols( + namespace: str, + project: str, + tag: Optional[str] = DEFAULT_TAG, + schema: str = '' +): + """ + Standardize PEP metadata column headers using BEDmess. + + Args: + - pep (str): PEP string to be standardized + - schema (str): Schema for AttrStandardizer + + Returns: + - dict: Standardized results + """ + + if schema == '': + return {} + + path = namespace + '/' + project + ':' + tag + print(path) + + model = AttrStandardizer(schema) + + results = model.standardize(pep=path) + return {"results": results} + + + diff --git a/web/src/api/project.ts b/web/src/api/project.ts index 18927cda..f14003db 100644 --- a/web/src/api/project.ts +++ b/web/src/api/project.ts @@ -85,6 +85,14 @@ export type RestoreProjectFromHistoryResponse = { registry: string; }; +export type StandardizeColsResponse = { + results: { + [key: string]: { + [key: string]: number; + }; + }; +}; + export const getProject = ( namespace: string, projectName: string, @@ -404,3 +412,16 @@ export const restoreProjectFromHistory = ( const url = `${API_BASE}/projects/${namespace}/${name}/history/${historyId}/restore?tag=${tag}`; return axios.post(url, {}, { headers: { Authorization: `Bearer ${jwt}` } }); }; + +export const getStandardizedCols = ( + namespace: string, + name: string, + tag: string, + jwt: string | null, + schema: string +) => { + const url = `${API_BASE}/projects/${namespace}/${name}/standardize`; + return axios + .get(url, { headers: { Authorization: `Bearer ${jwt}` } }) + .then((res) => res.data); +}; diff --git a/web/src/components/modals/standardize-metadata.tsx b/web/src/components/modals/standardize-metadata.tsx index 8de9095d..1f1ba62d 100644 --- a/web/src/components/modals/standardize-metadata.tsx +++ b/web/src/components/modals/standardize-metadata.tsx @@ -5,6 +5,7 @@ import { useProjectAnnotation } from '../../hooks/queries/useProjectAnnotation'; import { useSampleTable } from '../../hooks/queries/useSampleTable'; import { ProjectMetaEditForm } from '../forms/edit-project-meta'; import { arraysToSampleList, sampleListToArrays } from '../../utils/sample-table'; +import { useStandardize } from '../../hooks/queries/useStandardize'; import { HotTable } from '@handsontable/react'; import Handsontable from 'handsontable'; @@ -24,6 +25,10 @@ type Props = { setNewSamples: (samples: any[][]) => void; }; +type TabDataRow = string[]; +type TabData = TabDataRow[]; +type SelectedValues = Record; + export const StandardizeMetadataModal = (props: Props) => { const { namespace, project, tag, show, onHide, sampleTable, sampleTableIndex, newSamples, setNewSamples } = props; @@ -39,10 +44,21 @@ export const StandardizeMetadataModal = (props: Props) => { // console.log(tabData) // console.log(sampleTableIndex) - const data: DynamicData = { - 'sample_type': { 'Not Predictable': 0.0 }, - 'genome': { 'biospecimen_class_term_label': 0.9097071290016174, 'prediction_2': 0.321412348, 'sample_type': 0.12534} - }; + // const [selectedOption, setSelectedOption] = useState({ value: 'ENCODE', label: 'ENCODE' }); + const [selectedOption, setSelectedOption] = useState(null); + const selectRef = React.useRef(null); + + const { isLoading, isError, error, data, refetch } = useStandardize( + namespace, + project, + tag, + selectedOption?.value, + ); + + const standardizedData = data?.results + + console.log(selectedOption) + console.log(standardizedData) const formatToPercentage = (value: number): string => { return `${(value * 100).toFixed(2)}%`; @@ -83,26 +99,16 @@ export const StandardizeMetadataModal = (props: Props) => { }; const getDefaultSelections = () => { + if (!standardizedData) { + return {}; + } const defaultSelections = {}; - Object.keys(data).forEach(key => { - // if (data[key]['Not Predictable'] === 0) { - // defaultSelections[key] = key; // Use the key itself as the default value - // } else { - // const options = Object.entries(data[key]); - // const [bestOption] = options.reduce((best, current) => - // current[1] > best[1] && current[0] !== 'Not Predictable' ? current : best - // ); - // defaultSelections[key] = bestOption; - // } + Object.keys(standardizedData).forEach(key => { defaultSelections[key] = key; }); return defaultSelections; }; - type TabDataRow = string[]; - type TabData = TabDataRow[]; - type SelectedValues = Record; - const updateTabDataRaw = (tabDataRaw: TabData, selectedValues: SelectedValues): TabData => { if (tabDataRaw.length === 0) return tabDataRaw; @@ -120,7 +126,7 @@ export const StandardizeMetadataModal = (props: Props) => { return updatedTabDataRaw; }; - function prepareHandsontableData(key, selectedValues, tabData) { + const prepareHandsontableData = (key, selectedValues, tabData) => { const selectedValue = selectedValues[key] || ''; const topValues = tabData[key]?.slice(0, 6).map(item => [item]) || []; const emptyRows = Array(Math.max(0, 6 - topValues.length)).fill(['']); @@ -132,6 +138,16 @@ export const StandardizeMetadataModal = (props: Props) => { ]; } + const handleSubmit = async (event) => { + event.preventDefault(); + if (selectRef.current) { + const currentValue = selectRef.current.getValue()[0]; + setSelectedOption(currentValue); + console.log('Form submitted with:', currentValue); + // Refetch data with new selectedOption + await refetch(); + } + }; const [selectedValues, setSelectedValues] = useState(getDefaultSelections()); const [whereDuplicates, setWhereDuplicates] = useState(null) @@ -156,106 +172,118 @@ export const StandardizeMetadataModal = (props: Props) => {
Standardizer Schema
-
-
- + + +
+
({ - ...provided, - borderRadius: '.333333em', // Left radii set to 0, right radii kept at 4px - }), - }} - options={ - [ - { value: 'ENCODE', label: 'ENCODE' }, - { value: 'Fairtracks', label: 'Fairtracks' }, - { value: 'vanilla', label: 'Vanilla' } - ] - } - /> + className="top-z w-100 ms-1" + ref={selectRef} + styles={{ + control: (provided) => ({ + ...provided, + borderRadius: '.333333em', // Left radii set to 0, right radii kept at 4px + }), + }} + options={ + [ + { value: 'ENCODE', label: 'ENCODE' }, + { value: 'FAIRTRACKS', label: 'Fairtracks' } + ] + } + defaultValue={selectedOption} + />
- + + +
+
-
+
-
+ {standardizedData ? -
-
-
Original Column
-
-
-
Predicted Column Header
+ <> +
+ +
+
+
Original Column
+
+
+
Predicted Column Header
+
-
-
- {Object.keys(data).map((key, index) => ( -
- -
- -
- -
- + {Object.keys(standardizedData).map((key, index) => ( +
+ +
+ +
+ +
+ -
- -
-
-
-
- handleRadioChange(key, null)} + ]} + licenseKey="non-commercial-and-evaluation" + className='custom-handsontable' /> - +
- {Object.entries(data[key]).map(([subKey, value], index, array) => ( - - +
+
+
+
+ handleRadioChange(key, null)} + /> + + + {Object.entries(standardizedData[key]).map(([subKey, value], index, array) => ( + { id={`${key}-suggested-${subKey}`} value={subKey} defaultChecked={selectedValues[key] === subKey} - disabled={data[key]['Not Predictable'] === 0} + disabled={standardizedData[key]['Not Predictable'] === 0} onChange={() => handleRadioChange(key, subKey)} /> - - ))} + + ))} + +
+
-
-
- ))} - + ))} + + + : null + } diff --git a/web/src/globals.css b/web/src/globals.css index 907300f0..868f7dc3 100644 --- a/web/src/globals.css +++ b/web/src/globals.css @@ -729,9 +729,4 @@ body { border-bottom: none !important; } -.btn-group-vertical { - bottom: -1px; -} - - diff --git a/web/src/hooks/queries/useStandardize.ts b/web/src/hooks/queries/useStandardize.ts new file mode 100644 index 00000000..1e31d150 --- /dev/null +++ b/web/src/hooks/queries/useStandardize.ts @@ -0,0 +1,14 @@ +import { useQuery } from '@tanstack/react-query'; + +import { getStandardizedCols } from '../../api/project'; +import { useSession } from '../../contexts/session-context'; + +export const useStandardize = (namespace: string | undefined, project: string | undefined, tag: string | undefined, schema: string | undefined) => { + const session = useSession(); + const query = useQuery({ + queryKey: [namespace, project, tag], + queryFn: () => getStandardizedCols(namespace || '', project || '', tag, session?.jwt || '', schema || ''), + enabled: namespace !== undefined || (project !== undefined && session?.jwt !== null) || schema !== null, + }); + return query; +}; From a69d512654f15847dc19bc8bd87e7b4d0242b6ea Mon Sep 17 00:00:00 2001 From: Nathan LeRoy Date: Fri, 16 Aug 2024 12:23:23 -0400 Subject: [PATCH 10/49] small tweaks --- .../modals/standardize-metadata.tsx | 363 +++++++++--------- web/src/hooks/queries/useStandardize.ts | 9 +- 2 files changed, 183 insertions(+), 189 deletions(-) diff --git a/web/src/components/modals/standardize-metadata.tsx b/web/src/components/modals/standardize-metadata.tsx index 1f1ba62d..7fd19bd0 100644 --- a/web/src/components/modals/standardize-metadata.tsx +++ b/web/src/components/modals/standardize-metadata.tsx @@ -1,17 +1,16 @@ +import { HotTable } from '@handsontable/react'; +import Handsontable from 'handsontable'; +import 'handsontable/dist/handsontable.full.css'; +import React, { FormEvent, useState } from 'react'; import { Modal } from 'react-bootstrap'; -import React, { useState } from 'react'; +import ReactSelect from 'react-select'; + import { useEditProjectMetaMutation } from '../../hooks/mutations/useEditProjectMetaMutation'; import { useProjectAnnotation } from '../../hooks/queries/useProjectAnnotation'; import { useSampleTable } from '../../hooks/queries/useSampleTable'; -import { ProjectMetaEditForm } from '../forms/edit-project-meta'; -import { arraysToSampleList, sampleListToArrays } from '../../utils/sample-table'; import { useStandardize } from '../../hooks/queries/useStandardize'; - -import { HotTable } from '@handsontable/react'; -import Handsontable from 'handsontable'; -import 'handsontable/dist/handsontable.full.css'; - -import ReactSelect from 'react-select'; +import { arraysToSampleList, sampleListToArrays } from '../../utils/sample-table'; +import { ProjectMetaEditForm } from '../forms/edit-project-meta'; type Props = { namespace: string; @@ -28,51 +27,54 @@ type Props = { type TabDataRow = string[]; type TabData = TabDataRow[]; type SelectedValues = Record; +type AvailableSchemas = 'ENCODE' | 'FAIRTRACKS'; export const StandardizeMetadataModal = (props: Props) => { const { namespace, project, tag, show, onHide, sampleTable, sampleTableIndex, newSamples, setNewSamples } = props; // const tabDataRaw = sampleListToArrays(sampleTable?.items || []) const tabDataRaw = newSamples; - const tabData = tabDataRaw[0].map((_, colIndex) => tabDataRaw.map(row => row[colIndex])).reduce((obj, row) => { - const [key, ...values] = row; - obj[key] = values; - return obj; - }, {}); + const tabData = tabDataRaw[0] + .map((_, colIndex) => tabDataRaw.map((row) => row[colIndex])) + .reduce((obj, row) => { + const [key, ...values] = row; + obj[key] = values; + return obj; + }, {}); // console.log(tabDataRaw) // console.log(tabData) // console.log(sampleTableIndex) // const [selectedOption, setSelectedOption] = useState({ value: 'ENCODE', label: 'ENCODE' }); - const [selectedOption, setSelectedOption] = useState(null); - const selectRef = React.useRef(null); - - const { isLoading, isError, error, data, refetch } = useStandardize( - namespace, - project, - tag, - selectedOption?.value, - ); + const [selectedOption, setSelectedOption] = useState('ENCODE'); + + const { + isLoading, + isError, + error, + data, + refetch: standardize, + } = useStandardize(namespace, project, tag, selectedOption?.value); - const standardizedData = data?.results + const standardizedData = data?.results; - console.log(selectedOption) - console.log(standardizedData) + console.log(selectedOption); + console.log(standardizedData); const formatToPercentage = (value: number): string => { return `${(value * 100).toFixed(2)}%`; }; const handleRadioChange = (key, value) => { - setSelectedValues(prev => { + setSelectedValues((prev) => { const newValues = { ...prev, - [key]: value === null ? key : value + [key]: value === null ? key : value, }; - setWhereDuplicates(checkForDuplicates(newValues)) - + setWhereDuplicates(checkForDuplicates(newValues)); + return newValues; }); }; @@ -103,7 +105,7 @@ export const StandardizeMetadataModal = (props: Props) => { return {}; } const defaultSelections = {}; - Object.keys(standardizedData).forEach(key => { + Object.keys(standardizedData).forEach((key) => { defaultSelections[key] = key; }); return defaultSelections; @@ -128,29 +130,22 @@ export const StandardizeMetadataModal = (props: Props) => { const prepareHandsontableData = (key, selectedValues, tabData) => { const selectedValue = selectedValues[key] || ''; - const topValues = tabData[key]?.slice(0, 6).map(item => [item]) || []; + const topValues = tabData[key]?.slice(0, 6).map((item) => [item]) || []; const emptyRows = Array(Math.max(0, 6 - topValues.length)).fill(['']); - return [ - [selectedValue], - ...topValues, - ...emptyRows - ]; - } + return [[selectedValue], ...topValues, ...emptyRows]; + }; - const handleSubmit = async (event) => { + const handleSubmit = async (event: FormEvent) => { event.preventDefault(); - if (selectRef.current) { - const currentValue = selectRef.current.getValue()[0]; - setSelectedOption(currentValue); - console.log('Form submitted with:', currentValue); - // Refetch data with new selectedOption - await refetch(); - } + console.log('Form submitted with:', selectedOption); + + // Refetch data with new selectedOption + await standardize(); }; const [selectedValues, setSelectedValues] = useState(getDefaultSelections()); - const [whereDuplicates, setWhereDuplicates] = useState(null) + const [whereDuplicates, setWhereDuplicates] = useState(null); return ( @@ -158,192 +153,189 @@ export const StandardizeMetadataModal = (props: Props) => { Metadata Standardizer -
-
-

Use the metadata standardizer powered by BEDmess to bring consistency across metadata columns in all of your projects. - Compare predicted suggestions below (accuracy indicated in parenthesis) and choose whether to keep or discard them. - Column contents [previewed in brackets] are not changed by the standardizer. - After accepting the changes, save your project for them to take effect.

+
+
+

+ Use the metadata standardizer powered by BEDmess to bring consistency across metadata columns in all of + your projects. Compare predicted suggestions below (accuracy indicated in parenthesis) and choose whether + to keep or discard them. Column contents [previewed in brackets] are not changed by the standardizer. + After accepting the changes, save your project for them to take effect. +

-
+
+
+
+
Standardizer Schema
-
-
-
Standardizer Schema
-
-
-
+
+
({ ...provided, borderRadius: '.333333em', // Left radii set to 0, right radii kept at 4px }), }} - options={ - [ - { value: 'ENCODE', label: 'ENCODE' }, - { value: 'FAIRTRACKS', label: 'Fairtracks' } - ] - } + options={[ + // @ts-ignore + { value: 'ENCODE', label: 'ENCODE' }, + // @ts-ignore + { value: 'FAIRTRACKS', label: 'Fairtracks' }, + ]} defaultValue={selectedOption} + value={selectedOption} + onChange={(selectedOption) => { + if (selectedOption === null) { + return; + } + setSelectedOption(selectedOption); + }} /> -
-
- - -
- +
+ +
- {standardizedData ? - - <> -
+ {standardizedData ? ( + <> +
-
-
-
Original Column
-
-
-
Predicted Column Header
+
+
+
Original Column
+
+
+
Predicted Column Header
+
-
- -
- {Object.keys(standardizedData).map((key, index) => ( -
- -
- -
- -
- + {Object.keys(standardizedData).map((key, index) => ( +
+
+
+
+ -
- -
-
-
-
- handleRadioChange(key, null)} + }, + }, + ]} + licenseKey="non-commercial-and-evaluation" + className="custom-handsontable" /> - - - {Object.entries(standardizedData[key]).map(([subKey, value], index, array) => ( - - handleRadioChange(key, subKey)} - /> - - - ))} -
+
+
+
+ handleRadioChange(key, null)} + /> + + + {Object.entries(standardizedData[key]).map(([subKey, value], index, array) => ( + + handleRadioChange(key, subKey)} + /> + + + ))} +
+
+
+
-
-
- ))} - - - : null - } - + ))} + + + ) : null} - {whereDuplicates !== null && ( -
- Warning: ensure no duplicate column names have been selected. -
+
Warning: ensure no duplicate column names have been selected.
)} - - -
- ); }; diff --git a/web/src/hooks/queries/useStandardize.ts b/web/src/hooks/queries/useStandardize.ts index 1e31d150..e041f88c 100644 --- a/web/src/hooks/queries/useStandardize.ts +++ b/web/src/hooks/queries/useStandardize.ts @@ -3,12 +3,17 @@ import { useQuery } from '@tanstack/react-query'; import { getStandardizedCols } from '../../api/project'; import { useSession } from '../../contexts/session-context'; -export const useStandardize = (namespace: string | undefined, project: string | undefined, tag: string | undefined, schema: string | undefined) => { +export const useStandardize = ( + namespace: string | undefined, + project: string | undefined, + tag: string | undefined, + schema: string | undefined, +) => { const session = useSession(); const query = useQuery({ queryKey: [namespace, project, tag], queryFn: () => getStandardizedCols(namespace || '', project || '', tag, session?.jwt || '', schema || ''), - enabled: namespace !== undefined || (project !== undefined && session?.jwt !== null) || schema !== null, + enabled: false, // This query should only run on demand (ie. when the user clicks the standardize button) }); return query; }; From 3030166694e20cdba09512f9cb3a53a31cfada50 Mon Sep 17 00:00:00 2001 From: Sam Park Date: Fri, 16 Aug 2024 16:49:27 -0400 Subject: [PATCH 11/49] metadata standardizer api integration --- web/src/api/project.ts | 4 +- .../modals/standardize-metadata.tsx | 93 +++++++++---------- .../components/spinners/loading-spinner.tsx | 23 +++-- web/src/globals.css | 17 ++++ 4 files changed, 75 insertions(+), 62 deletions(-) diff --git a/web/src/api/project.ts b/web/src/api/project.ts index f14003db..67533a5d 100644 --- a/web/src/api/project.ts +++ b/web/src/api/project.ts @@ -418,9 +418,9 @@ export const getStandardizedCols = ( name: string, tag: string, jwt: string | null, - schema: string + schema: string, ) => { - const url = `${API_BASE}/projects/${namespace}/${name}/standardize`; + const url = `${API_BASE}/projects/${namespace}/${name}/standardize?schema=${schema}&tag=${tag}`; return axios .get(url, { headers: { Authorization: `Bearer ${jwt}` } }) .then((res) => res.data); diff --git a/web/src/components/modals/standardize-metadata.tsx b/web/src/components/modals/standardize-metadata.tsx index 7fd19bd0..99c8ba5a 100644 --- a/web/src/components/modals/standardize-metadata.tsx +++ b/web/src/components/modals/standardize-metadata.tsx @@ -1,7 +1,7 @@ import { HotTable } from '@handsontable/react'; import Handsontable from 'handsontable'; import 'handsontable/dist/handsontable.full.css'; -import React, { FormEvent, useState } from 'react'; +import React, { FormEvent, useState, useEffect, useCallback } from 'react'; import { Modal } from 'react-bootstrap'; import ReactSelect from 'react-select'; @@ -11,6 +11,7 @@ import { useSampleTable } from '../../hooks/queries/useSampleTable'; import { useStandardize } from '../../hooks/queries/useStandardize'; import { arraysToSampleList, sampleListToArrays } from '../../utils/sample-table'; import { ProjectMetaEditForm } from '../forms/edit-project-meta'; +import { LoadingSpinner } from '../spinners/loading-spinner' type Props = { namespace: string; @@ -42,15 +43,12 @@ export const StandardizeMetadataModal = (props: Props) => { return obj; }, {}); - // console.log(tabDataRaw) - // console.log(tabData) - // console.log(sampleTableIndex) - - // const [selectedOption, setSelectedOption] = useState({ value: 'ENCODE', label: 'ENCODE' }); - const [selectedOption, setSelectedOption] = useState('ENCODE'); + const [selectedOption, setSelectedOption] = useState(''); + const [selectedValues, setSelectedValues] = useState({}); + const [whereDuplicates, setWhereDuplicates] = useState(null); const { - isLoading, + isFetching, isError, error, data, @@ -59,9 +57,6 @@ export const StandardizeMetadataModal = (props: Props) => { const standardizedData = data?.results; - console.log(selectedOption); - console.log(standardizedData); - const formatToPercentage = (value: number): string => { return `${(value * 100).toFixed(2)}%`; }; @@ -100,10 +95,7 @@ export const StandardizeMetadataModal = (props: Props) => { return result.length > 0 ? result : null; }; - const getDefaultSelections = () => { - if (!standardizedData) { - return {}; - } + const getDefaultSelections = (standardizedData) => { const defaultSelections = {}; Object.keys(standardizedData).forEach((key) => { defaultSelections[key] = key; @@ -128,24 +120,28 @@ export const StandardizeMetadataModal = (props: Props) => { return updatedTabDataRaw; }; - const prepareHandsontableData = (key, selectedValues, tabData) => { - const selectedValue = selectedValues[key] || ''; - const topValues = tabData[key]?.slice(0, 6).map((item) => [item]) || []; - const emptyRows = Array(Math.max(0, 6 - topValues.length)).fill(['']); - - return [[selectedValue], ...topValues, ...emptyRows]; - }; - const handleSubmit = async (event: FormEvent) => { event.preventDefault(); - console.log('Form submitted with:', selectedOption); - // Refetch data with new selectedOption await standardize(); }; - const [selectedValues, setSelectedValues] = useState(getDefaultSelections()); - const [whereDuplicates, setWhereDuplicates] = useState(null); + const prepareHandsontableData = useCallback((key: string) => { + const selectedValue = selectedValues[key] || ''; + const topValues = tabData[key]?.slice(0, 6).map((item) => [item]) || []; + const emptyRows = Array(Math.max(0, 6 - topValues.length)).fill(['']); + + return [[selectedValue], ...topValues, ...emptyRows]; + }, [selectedValues, tabData]); + + useEffect(() => { + if (data?.results) { + const defaultSelections = getDefaultSelections(data.results); + setWhereDuplicates(checkForDuplicates(defaultSelections)); + console.log('Setting default selections:', defaultSelections); + setSelectedValues(defaultSelections); + } + }, [data]); return ( @@ -157,7 +153,7 @@ export const StandardizeMetadataModal = (props: Props) => {

Use the metadata standardizer powered by BEDmess to bring consistency across metadata columns in all of - your projects. Compare predicted suggestions below (accuracy indicated in parenthesis) and choose whether + your projects. After choosing a standardizer schema below, compare predicted suggestions (accuracy indicated in parenthesis) and choose whether to keep or discard them. Column contents [previewed in brackets] are not changed by the standardizer. After accepting the changes, save your project for them to take effect.

@@ -206,7 +202,7 @@ export const StandardizeMetadataModal = (props: Props) => {
- {standardizedData ? ( + {standardizedData && !isFetching ? ( <>
@@ -223,13 +219,14 @@ export const StandardizeMetadataModal = (props: Props) => { {Object.keys(standardizedData).map((key, index) => (
+ {key === sampleTableIndex ?

SampleTableIndex must also be updated in project config!

: null}
{ td.style.color = 'red'; } } - }, - }, + } + } ]} licenseKey="non-commercial-and-evaluation" className="custom-handsontable" @@ -270,7 +267,7 @@ export const StandardizeMetadataModal = (props: Props) => { name={key} id={`${key}-original`} value={key} - defaultChecked={selectedValues[key] === key} // Check if the selected value is the same as the key + checked={selectedValues[key] === key} // Check if the selected value is the same as the key onChange={() => handleRadioChange(key, null)} />
*/} -
- - +
+ +
@@ -106,21 +106,14 @@ export const CreateSchemaForm = (props: Props) => { />
-

- * Namespace and Schema Name are required. -

-
- - - + {/* Add a dropdown here */}
= ({ onHide, defaultNamespace }) => { })}
) : null} +

+ * Namespace and Project Name are required. A tag value of "default" will be supplied if the Tag input is left empty. +

+ +
+ +
- - - - ); }; From 1e1f8e63e0f7102e3294450bc0bba413d46ed65b Mon Sep 17 00:00:00 2001 From: Sam Park Date: Wed, 4 Sep 2024 00:37:36 -0400 Subject: [PATCH 40/49] ui polish --- .../project-cards/project-card-dropdown.tsx | 4 +- .../namespace/project-cards/project-card.tsx | 16 ++--- .../components/namespace/view-selector.tsx | 8 +-- .../schemas/schema-card-dropdown.tsx | 2 +- web/src/globals.css | 70 ++++++++++++++++--- web/src/pages/Namespace.tsx | 12 ++-- 6 files changed, 82 insertions(+), 30 deletions(-) diff --git a/web/src/components/namespace/project-cards/project-card-dropdown.tsx b/web/src/components/namespace/project-cards/project-card-dropdown.tsx index 4b2f3990..46831de2 100644 --- a/web/src/components/namespace/project-cards/project-card-dropdown.tsx +++ b/web/src/components/namespace/project-cards/project-card-dropdown.tsx @@ -33,7 +33,7 @@ export const ProjectCardDropdown: FC = (props) => {
) : ( From 04f61b935ef3001512abe10957a3f14013564851 Mon Sep 17 00:00:00 2001 From: Sam Park Date: Wed, 4 Sep 2024 02:08:41 -0400 Subject: [PATCH 45/49] class changes --- .../namespace/project-cards/project-card-dropdown.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/components/namespace/project-cards/project-card-dropdown.tsx b/web/src/components/namespace/project-cards/project-card-dropdown.tsx index 46831de2..05548197 100644 --- a/web/src/components/namespace/project-cards/project-card-dropdown.tsx +++ b/web/src/components/namespace/project-cards/project-card-dropdown.tsx @@ -32,7 +32,7 @@ export const ProjectCardDropdown: FC = (props) => { Date: Wed, 4 Sep 2024 10:49:54 -0400 Subject: [PATCH 46/49] edit metadata modal update --- pephub/routers/api/v1/project.py | 3 -- .../components/forms/edit-project-meta.tsx | 30 ++++++---------- .../components/modals/edit-meta-metadata.tsx | 34 +++++++++++++------ .../components/namespace/view-selector.tsx | 2 +- web/src/globals.css | 2 +- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/pephub/routers/api/v1/project.py b/pephub/routers/api/v1/project.py index 34532e76..09d572ca 100644 --- a/pephub/routers/api/v1/project.py +++ b/pephub/routers/api/v1/project.py @@ -1171,11 +1171,8 @@ async def get_standardized_cols( prj = peppy.Project.from_dict(pep) model = AttrStandardizer(schema) - print(prj.sample_table) - print(pep) try: results = model.standardize(pep=prj) - print(results) except Exception: raise HTTPException( code=400, diff --git a/web/src/components/forms/edit-project-meta.tsx b/web/src/components/forms/edit-project-meta.tsx index 2dca7132..f3fa5ec1 100644 --- a/web/src/components/forms/edit-project-meta.tsx +++ b/web/src/components/forms/edit-project-meta.tsx @@ -68,7 +68,7 @@ export const ProjectMetaEditForm = (props: Props) => { return (
-
+
@@ -80,16 +80,14 @@ export const ProjectMetaEditForm = (props: Props) => { id="is-private-toggle" />
-
+
-
- +
+ { render={({ message }) => (message ?

{message}

: null)} />
-
- +
+
{ />
-
- +
+ { render={({ message }) => (message ?

{message}

: null)} />
-
- +
+ { id="metadata-save-btn" disabled={(!isDirty && isValid) || !!errors.name?.message || !!errors.tag?.message || isSubmitting} type="button" - className="btn btn-success me-1" + className="btn btn-success" > {isSubmitting ? 'Saving...' : 'Save'} diff --git a/web/src/components/modals/edit-meta-metadata.tsx b/web/src/components/modals/edit-meta-metadata.tsx index 2d7395cb..80d1b12a 100644 --- a/web/src/components/modals/edit-meta-metadata.tsx +++ b/web/src/components/modals/edit-meta-metadata.tsx @@ -35,18 +35,30 @@ export const EditMetaMetadataModal = (props: Props) => { return ( - - Edit Metadata - - +
+

Edit Metadata

+ +

+
+ + +
); diff --git a/web/src/components/namespace/view-selector.tsx b/web/src/components/namespace/view-selector.tsx index 4c20cbc9..6a09bec9 100644 --- a/web/src/components/namespace/view-selector.tsx +++ b/web/src/components/namespace/view-selector.tsx @@ -27,7 +27,7 @@ export const NamespaceViewSelector: FC = (props) => { }; return ( -
+
- +
- -

{message}

} /> +