Skip to content

Commit

Permalink
Merge pull request #365 from pepkit/dev_sam
Browse files Browse the repository at this point in the history
Merge UI fixes and metadata standardizer
  • Loading branch information
sanghoonio authored Sep 5, 2024
2 parents 0689df3 + cdd444b commit 015f4e0
Show file tree
Hide file tree
Showing 48 changed files with 1,951 additions and 749 deletions.
42 changes: 42 additions & 0 deletions pephub/routers/api/v1/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@
ProjectHistoryResponse,
SamplesResponseModel,
ConfigResponseModel,
StandardizerResponse,
)
from .helpers import verify_updated_project

from attribute_standardizer.attr_standardizer_class import AttrStandardizer

_LOGGER = logging.getLogger(__name__)

load_dotenv()
Expand Down Expand Up @@ -1140,3 +1143,42 @@ def delete_full_history(
status_code=400,
detail="Could not delete history. Server error.",
)


@project.post(
"/standardize",
summary="Standardize PEP metadata column headers",
response_model=StandardizerResponse,
)
async def get_standardized_cols(
pep: peppy.Project = Depends(get_project),
schema: str = "",
):
"""
Standardize PEP metadata column headers using BEDmess.
:param namespace: pep: PEP string to be standardized
:param schema: Schema for AttrStandardizer
:return dict: Standardized results
"""

if schema == "":
raise HTTPException(
code=500,
detail="Schema is required! Available schemas are ENCODE and Fairtracks",
)
return {}

prj = peppy.Project.from_dict(pep)
model = AttrStandardizer(schema)

try:
results = model.standardize(pep=prj)
except Exception:
raise HTTPException(
code=400,
detail=f"Error standardizing PEP.",
)

return StandardizerResponse(results=results)
4 changes: 4 additions & 0 deletions pephub/routers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,7 @@ class SchemaGetResponse(BaseModel):
description: Optional[str] = None
last_update_date: str = ""
submission_date: str = ""


class StandardizerResponse(BaseModel):
results: dict = {}
3 changes: 2 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"axios": "^1.3.4",
"bootstrap": "^5.2.3",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.10.3",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.8.0",
Expand Down Expand Up @@ -57,6 +57,7 @@
"devDependencies": {
"@mdx-js/rollup": "^3.0.0",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/js-yaml": "^4.0.9",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react-swc": "^3.0.0",
Expand Down
21 changes: 21 additions & 0 deletions web/src/api/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -404,3 +412,16 @@ export const restoreProjectFromHistory = (
const url = `${API_BASE}/projects/${namespace}/${name}/history/${historyId}/restore?tag=${tag}`;
return axios.post<RestoreProjectFromHistoryResponse>(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?schema=${schema}&tag=${tag}`;
return axios
.post<StandardizeColsResponse>(url, { headers: { Authorization: `Bearer ${jwt || 'NO_AUTHORIZATION'}` } })
.then((res) => res.data);
};
79 changes: 55 additions & 24 deletions web/src/components/forms/blank-project-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const CombinedErrorMessage = (props: CombinedErrorMessageProps) => {
}

if (nameError || tagError) {
return <p className="text-danger text-xs pt-1">{msg}</p>;
return <p className="text-danger text-xs pt-1 mb-0">{msg}</p>;
}

return null;
Expand Down Expand Up @@ -106,27 +106,27 @@ sample_table: samples.csv
const { isPending: isSubmitting, submit } = useBlankProjectFormMutation(namespace);

return (
<form id="blank-project-form" className="border-0 form-control">
<div className="mb-3 mt-3 form-check form-switch">
<form id="blank-project-form" className="border-0 form-control p-0">
<div className="mt-3 form-check form-switch">
<input
className="form-check-input"
type="checkbox"
role="switch"
id="blank-is-private-toggle"
{...register('is_private')}
/>
<label className="form-check-label">
<label className="form-check-label text-sm">
<i className="bi bi-lock"></i>
Private
</label>
</div>
<div className="namespace-name-tag-container">
<label className="fw-bold text-sm">Namespace *</label>
<label className="fw-bold text-sm">Name *</label>
<label className="fw-bold text-sm">Tag</label>
<div className="namespace-name-tag-container mt-2">
<label className="fw-semibold text-sm">Namespace*</label>
<label className="fw-semibold text-sm">Name*</label>
<label className="fw-semibold text-sm">Tag</label>
</div>
<div className="namespace-name-tag-container fs-4 w-full">
<div className="d-flex flex-row align-items-center justify-content-between w-full ">
<div className="d-flex flex-row align-items-center justify-content-between w-full">
<select
id="blank-namespace-select"
className="form-select"
Expand All @@ -142,14 +142,20 @@ sample_table: samples.csv
</select>
<span className="mx-1 mb-1">/</span>
</div>
<div className="d-flex flex-row align-items-center justify-content-between w-full ">
<div className="d-flex flex-row align-items-center justify-content-between w-full">
<input
// dont allow any whitespace
{...register('project_name', {
required: true,
required: {
value: true,
message: "empty",
},
pattern: {
value: /^\S+$/,
message: 'No spaces allowed.',
value: /^[a-zA-Z0-9_-]+$/,
message: "invalid",
},
})}
id="blank-project-name"
Expand All @@ -159,20 +165,29 @@ sample_table: samples.csv
/>
<span className="mx-1 mb-1">:</span>
</div>
<input {...register('tag')} id="blank_tag" type="text" className="form-control" placeholder="default" />
<input {...register('tag', {
required: false,
pattern: {
value: /^[a-zA-Z0-9_-]+$/,
message: "invalid",
},
})}
id="blank_tag"
type="text"
className="form-control"
placeholder="default"
/>
</div>
<ErrorMessage errors={errors} name="project_name" render={({ message }) => <p>{message}</p>} />
<CombinedErrorMessage errors={errors}/>
<label className="fw-semibold text-sm mt-2">Description</label>
<textarea
id="blank_description"
className="form-control mt-3"
className="form-control"
rows={3}
placeholder="Describe your PEP."
{...register('description')}
></textarea>
<label className="form-check-label mt-3 mb-1">
<i className="bi bi-file-earmark-break me-1"></i>
Schema
</label>
<label className="fw-semibold text-sm mt-2">Schema</label>
<div>
<Controller
control={control}
Expand All @@ -187,9 +202,9 @@ sample_table: samples.csv
)}
/>
</div>
<Tabs defaultActiveKey="samples" id="blank-project-tabs" className="mt-3">
<Tabs defaultActiveKey="samples" id="blank-project-tabs" className="mt-3 text-sm">
<Tab eventKey="samples" title="Samples">
<div className="p-2 -1">
<div className="overflow-auto border rounded-bottom-2 custom-handsontable" style={{marginTop: '-1px', zIndex: 99999}}>
<SampleTable
height={300}
data={sampleTable}
Expand All @@ -200,22 +215,25 @@ sample_table: samples.csv
</div>
</Tab>
<Tab eventKey="config" title="Config">
<div className="p-1 -0">
<div className="border rounded-bottom-2 pb-1">
<ProjectConfigEditor
value={configYAML}
setValue={(data) => {
setValue('config', data);
}}
height={300}
height={295}
/>
</div>
</Tab>
</Tabs>
<p className='text-xs mt-1'>
* Namespace and Project Name are required. A tag value of "default" will be supplied if the Tag input is left empty.
</p>
<div className="mt-3">
<button
disabled={!isValid || isSubmitting}
id="blank-project-submit-btn"
className="btn btn-success me-1"
className="btn btn-success float-end"
type="button"
onClick={() => {
try {
Expand All @@ -231,7 +249,20 @@ sample_table: samples.csv
onSuccess: onHide,
});
} catch (e) {
toast.error('Invalid sample table. ' + e);
toast((t) => (
<div className='my-1'>
<p><strong>{'The project could not be created.'}</strong></p>
{e instanceof Error ?
<p>{e.message + ''}</p> : <p>An unknown error occurred.</p>
}
<button className='btn btn-sm btn-danger float-end mt-3' onClick={() => toast.dismiss(t.id)}>
Dismiss
</button>
</div>
), {
duration: 16000,
position: 'top-right',
});
return;
}
}}
Expand All @@ -241,7 +272,7 @@ sample_table: samples.csv
</button>
<button
type="button"
className="btn btn-outline-dark me-1"
className="btn btn-outline-dark me-1 float-end"
data-bs-dismiss="modal"
onClick={() => {
resetForm();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ const SchemaDropdown: FC<Props> = ({ value, onChange, showDownload = true }) =>
onChange(newValue?.value || '');
}}
placeholder={isLoading ? 'Fetching schemas...' : 'Assign a schema...'}
isClearable
// isClearable
menuPlacement="top"
className="w-100"
styles={{
control: (provided) => ({
...provided,
borderRadius: '.33333em',
})
}}
/>
{showDownload && (
<a
Expand Down
Loading

0 comments on commit 015f4e0

Please sign in to comment.