Add the first production-ready version of the external project overview - #1185
Add the first production-ready version of the external project overview#1185nabalma wants to merge 8 commits into
Conversation
…view with submissions, readsets, dashboards, filters, and CSV exports
| def _get_readsets_queryset(self, parent_project): | ||
|
|
There was a problem hiding this comment.
a type annotation would be nice parent_project: ParentProject
| @action(detail=True, methods=["get"],url_path="readsets") | ||
| def overview_readsets(self, request, pk=None): |
There was a problem hiding this comment.
space after comma (methods=["get"],)
| class ParentProjectReadsetSerializer(serializers.Serializer): | ||
| id = serializers.IntegerField() | ||
| name = serializers.CharField() | ||
| readset_sample_name = serializers.CharField() | ||
| biosample_id = serializers.IntegerField(allow_null=True) | ||
| external_id = serializers.CharField() | ||
| run_name = serializers.CharField() | ||
| lane = serializers.IntegerField() | ||
| reference_genome_id = serializers.IntegerField(allow_null=True,) | ||
| reference_genome_assembly_name = serializers.CharField(allow_null=True,) | ||
| sequencing_index_name = serializers.CharField(allow_null=True) | ||
| run_start_date = serializers.DateField() | ||
| alias = serializers.CharField(allow_null=True) | ||
| cohort = serializers.CharField(allow_blank=True,allow_null=True,) | ||
| library_type = serializers.CharField(allow_null=True,) | ||
| container_barcodes = serializers.ListField(child=serializers.CharField(allow_null=True),allow_empty=True,) | ||
| number_of_reads = serializers.IntegerField(allow_null=True,) | ||
| number_of_bases = serializers.IntegerField(allow_null=True,) | ||
| average_quality = serializers.DecimalField( | ||
| max_digits=40, | ||
| decimal_places=20, | ||
| allow_null=True, | ||
| ) | ||
|
|
||
| pf_reads_aligned = serializers.DecimalField( | ||
| max_digits=40, | ||
| decimal_places=20, | ||
| allow_null=True, | ||
| ) | ||
|
|
||
| duplicate_aligned = serializers.DecimalField( | ||
| max_digits=40, | ||
| decimal_places=20, | ||
| allow_null=True, | ||
| ) | ||
| readset_files = serializers.ListField( | ||
| child=serializers.DictField(), | ||
| required=False, | ||
| ) | ||
| run_validation_status = serializers.IntegerField(allow_null=True,) |
There was a problem hiding this comment.
@UlysseFG Personally I feel this serializer should be a composition of models rather than individual fields. Like we could have a field for parent_project, derived_sample, readset, etc. So that way, we know where each fields come from.
| const openCount = data.filter((project) => project.status === "Open").length | ||
|
|
||
| const uniquePIs = new Set(data.map((project) => project.principal_investigator).filter(Boolean)) | ||
| .size | ||
|
|
||
| const uniqueRequestors = new Set(data.map((project) => project.requestor_name).filter(Boolean)) | ||
| .size |
There was a problem hiding this comment.
These should be memoized. Otherwise, they would get recomputed at each re-render of the component.
There was a problem hiding this comment.
Especially if these values are passed to child components.
There was a problem hiding this comment.
Fixed ...
const ExternalIDProjectsDashboard = ({ data }: ExternalIDProjectsDashboardProps) => {
const total = data.length
const openCount = useMemo(() => data.filter((project) => project.status === "Open").length, [data])
const uniquePIs = useMemo(() => new Set(data.map((project) => project.principal_investigator).filter(Boolean)).size, [data])
const uniqueRequestors = useMemo(() => new Set(data.map((project) => project.requestor_name).filter(Boolean)).size, [data])| const iconStyle = (color: string, backgroundColor: string): React.CSSProperties => ({ | ||
| color, | ||
| backgroundColor, | ||
| fontSize: 18, | ||
| padding: 6, | ||
| borderRadius: 8, | ||
| marginRight: 4, | ||
| }) |
There was a problem hiding this comment.
This can be outside the component definition.
There was a problem hiding this comment.
This style is now out of the component definition ...
const iconStyle = (color: string, backgroundColor: string): React.CSSProperties => ({
color,
backgroundColor,
fontSize: 18,
padding: 6,
borderRadius: 8,
marginRight: 4,
})
function ExternalIDReadSetDashboard({ readsets }: { readsets: ProjectOverviewReadset[] }) {| <Route | ||
| path="/external-projects-overview/*" | ||
| element={ | ||
| <PrivateNavigate> | ||
| <ExternalProjectsPage /> | ||
| </PrivateNavigate> | ||
| } | ||
| /> | ||
| <Route | ||
| path="/external-projects-overview/:parentProjectId" | ||
| element={ | ||
| <PrivateNavigate> | ||
| <ExternalProjectDetailsPage /> | ||
| </PrivateNavigate> | ||
| } | ||
| /> |
There was a problem hiding this comment.
We normally have nested Routes for specific pages like with frontend/src/components/projects/ProjectsPage.js. But since it's just two pages for now, we can keep it like this.
| expandedRowRender: (parentProject) => { | ||
| const internalProjects = (parentProject.projects ?? []).reduce<FMSProject[]>( | ||
| (projects, projectID) => { | ||
| const project = internalProjectsByID[projectID] | ||
| if (project) { | ||
| projects.push(project) | ||
| } | ||
| return projects | ||
| }, | ||
| [], | ||
| ) | ||
| return ( | ||
| <Table | ||
| size="small" | ||
| rowKey="id" | ||
| dataSource={internalProjects} | ||
| columns={internalProjectColumns} | ||
| pagination={false} | ||
| /> | ||
| ) | ||
| }, | ||
| }} |
There was a problem hiding this comment.
This makes me a little uncomfortable because dataSource is getting generated on the fly without memoization. But, so far, the performance is fine, so it's not so important to address this.
Summary
This PR introduces the first production-ready version of the External Project Overview,
Review comments from the previous stacked PRs have been addressed and incorporated in this PR.
This PR includes :
Review comments from the previous stacked PRs have been addressed and incorporated.