Skip to content

Commit

Permalink
Merge pull request Sage-Bionetworks#378 from nickgros/SWC-6492
Browse files Browse the repository at this point in the history
  • Loading branch information
nickgros authored Jul 24, 2023
2 parents 9c5d782 + b0b2d8d commit e1bfaaf
Show file tree
Hide file tree
Showing 29 changed files with 1,181 additions and 81 deletions.
1 change: 1 addition & 0 deletions packages/synapse-react-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"react-datetime": "^3.2.0",
"react-error-boundary": "^3.1.4",
"react-hot-toast": "2.2.0",
"react-inspector": "^6.0.2",
"react-intersection-observer": "^9.5.2",
"react-mailchimp-subscribe": "^2.1.3",
"react-measure": "^2.5.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import IconSvg from '../IconSvg'

type ExpandCollapseButtonProps = {
isExpanded: boolean
} & React.ButtonHTMLAttributes<HTMLButtonElement>

export default function ExpandCollapseButton(props: ExpandCollapseButtonProps) {
const { isExpanded, ...buttonProps } = props
return (
<button {...buttonProps}>
<IconSvg
icon={isExpanded ? 'minusBoxOutline' : 'addBoxOutline'}
sx={{
color: 'grey.600',
height: '16px',
verticalAlign: 'top',
'&:hover': {
color: 'grey.700',
},
}}
/>
</button>
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useRef, useState } from 'react'
import IconSvg from '../IconSvg/IconSvg'
import useResizeObserver from '@react-hook/resize-observer'
import ExpandCollapseButton from './ExpandCollapseButton'

export default function ExpandableTableDataCell(
props: JSX.IntrinsicElements['td'],
Expand Down Expand Up @@ -46,19 +46,11 @@ export default function ExpandableTableDataCell(
ref={tdRef}
>
{isOverflowingWhenNotExpanded && (
<button
<ExpandCollapseButton
isExpanded={isExpanded}
className="ExpandableTableData__expandButton"
onClick={() => setIsExpanded(!isExpanded)}
>
<IconSvg
icon={isExpanded ? 'minusBoxOutline' : 'addBoxOutline'}
sx={{
color: 'grey.600',
height: '16px',
verticalAlign: 'top',
}}
></IconSvg>
</button>
/>
)}
{props.children}
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { getUserProfileWithProfilePicAttached } from '../../utils/functions/getU
import { SynapseContextType } from '../../utils/context/SynapseContext'
import {
ColumnModel,
ColumnType,
ColumnTypeEnum,
EntityHeader,
FacetColumnRequest,
Expand All @@ -36,7 +35,7 @@ import ModalDownload from '../ModalDownload/ModalDownload'
import { QueryVisualizationContextType } from '../QueryVisualizationWrapper'
import { QueryContextType } from '../QueryContext/QueryContext'
import { Icon } from '../row_renderers/utils'
import { SynapseTableCell } from '../synapse_table_functions/SynapseTableCell'
import { SynapseTableCell } from './SynapseTableCell/SynapseTableCell'
import { Checkbox } from '../widgets/Checkbox'
import { EnumFacetFilter } from '../widgets/query-filter/EnumFacetFilter'
import {
Expand All @@ -48,6 +47,7 @@ import {
getColumnIndicesWithType,
getUniqueEntities,
isFileViewOrDataset,
isSortableColumn,
} from './SynapseTableUtils'
import { TablePagination } from './TablePagination'
import EntityIDColumnCopyIcon from './EntityIDColumnCopyIcon'
Expand Down Expand Up @@ -550,19 +550,24 @@ export class SynapseTable extends React.Component<
const columnLinkConfig = columnLinks.find(el => {
return el.matchColumnName === columnName
})
const columnType = headers[colIndex].columnType
const shouldWrapInExpandable = columnType !== ColumnTypeEnum.JSON // JSON handles its own overflow
const index = this.findSelectionIndex(
this.state.sortedColumnSelection,
columnName,
)
const isBold = index === -1 ? '' : 'SRC-boldText'
const TableDataCellElement = shouldWrapInExpandable
? ExpandableTableDataCell
: 'td'
if (isColumnActive) {
return (
<ExpandableTableDataCell
className="SRC_noBorderTop"
<TableDataCellElement
key={`(${rowIndex}${columnValue}${colIndex})`}
className="SRC_noBorderTop"
>
<SynapseTableCell
columnType={headers[colIndex].columnType}
columnType={columnType}
columnValue={columnValue}
isBold={isBold}
mapEntityIdToHeader={mapEntityIdToHeader}
Expand All @@ -575,7 +580,7 @@ export class SynapseTable extends React.Component<
rowId={row.rowId}
rowVersionNumber={row.versionNumber}
/>
</ExpandableTableDataCell>
</TableDataCellElement>
)
}
return <td className="SRC-hidden" key={`(${rowIndex},${colIndex})`} />
Expand Down Expand Up @@ -659,23 +664,6 @@ export class SynapseTable extends React.Component<
return rowsFormatted
}

public isSortableColumn(column: ColumnType) {
switch (column) {
case ColumnTypeEnum.USERID:
case ColumnTypeEnum.ENTITYID:
case ColumnTypeEnum.FILEHANDLEID:
case ColumnTypeEnum.STRING_LIST:
case ColumnTypeEnum.INTEGER_LIST:
case ColumnTypeEnum.BOOLEAN_LIST:
case ColumnTypeEnum.DATE_LIST:
case ColumnTypeEnum.USERID_LIST:
case ColumnTypeEnum.ENTITYID_LIST:
return false
default:
return true
}
}

private createTableHeader(
headers: SelectColumn[],
columnModels: ColumnModel[],
Expand Down Expand Up @@ -745,7 +733,7 @@ export class SynapseTable extends React.Component<
columnModel,
lastQueryRequest,
)}
{this.isSortableColumn(column.columnType) && (
{isSortableColumn(column.columnType) && (
<span
role="button"
aria-label="sort"
Expand All @@ -763,7 +751,7 @@ export class SynapseTable extends React.Component<
<Icon
type={ICON_STATE[columnIndex]}
cssClass={isSelectedIconClass}
></Icon>
/>
</span>
)}
{isEntityIDColumn && <EntityIDColumnCopyIcon />}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react'
import { useInView } from 'react-intersection-observer'
import { getEntityHeadersByIds } from '../synapse-client/SynapseClient'
import { useSynapseContext } from '../utils/context/SynapseContext'
import { EntityLink } from './EntityLink'
import { getEntityHeadersByIds } from '../../../synapse-client/SynapseClient'
import { useSynapseContext } from '../../../utils/context/SynapseContext'
import { EntityLink } from '../../EntityLink'

export type EntityIdListProps = {
entityIdList: string[]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
import { useInView } from 'react-intersection-observer'
import { getEvaluation } from '../synapse-client/SynapseClient'
import { useSynapseContext } from '../utils/context/SynapseContext'
import { getEvaluation } from '../../../synapse-client/SynapseClient'
import { useSynapseContext } from '../../../utils/context/SynapseContext'

export type EvaluationIdRendererProps = {
evaluationId: string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react'
import { Box, Typography } from '@mui/material'
import ExpandCollapseButton from '../../ExpandCollapseButton'
import { chromeLight, Inspector } from 'react-inspector'

type ComplexJSONRendererProps = {
value: any
}

/**
* Handles rendering any complex JSON value in a Synapse table cell. This generally acts as a fallback renderer if the
* JSON data doesn't have a simple structure handled by a different renderer.
* @param props
* @constructor
*/
export function ComplexJSONRenderer(props: ComplexJSONRendererProps) {
const { value } = props
const [expandAll, setExpandAll] = useState(false)

// @ts-expect-error - the theme prop type provided by react-inspector is wrong
const theme: React.ComponentProps<typeof Inspector>['theme'] = {
...chromeLight,
BASE_BACKGROUND_COLOR: 'none',
}

return (
<>
<Typography
variant={'smallText1'}
sx={{ py: 0.5, color: 'grey.600', fontStyle: 'italic' }}
>
{expandAll ? 'Collapse' : 'Expand'} all
<ExpandCollapseButton
className="ExpandableTableData__expandButton"
isExpanded={expandAll}
onClick={() => setExpandAll(v => !v)}
/>
</Typography>
<Box sx={{ pl: '2px' }}>
<Inspector
data={value}
theme={theme}
table={false}
key={
// Setting `expandLevel` to 0 doesn't trigger a re-render, so force the rerender by updating the key
String(expandAll)
}
expandLevel={
expandAll
? // making this number very large actually results in a performance hit, so choose a reasonably small number
20
: 0
}
/>
</Box>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { JSONPrimitiveType } from './JSONRendererUtils'
import React, { useState } from 'react'
import { Box, Collapse, Typography } from '@mui/material'
import { JSONPrimitiveRenderer } from './JSONPrimitiveRenderer'
import ExpandCollapseButton from '../../ExpandCollapseButton'

type JSONArrayRendererProps = { value: JSONPrimitiveType[] }

/**
* Handles rendering a JSON array of "primitive" types in a Synapse table cell.
* @param props
* @constructor
*/
export function JSONArrayRenderer(props: JSONArrayRendererProps) {
const { value } = props
const [expanded, setExpanded] = useState(false)
const showCollapseText = value.length > 1

if (value.length === 0) {
return (
<Typography variant={'smallText1'} className="SRC-inactive">
{'Empty array'}
</Typography>
)
}
if (value.length === 1) {
return <JSONPrimitiveRenderer value={value[0]} />
}
return (
<>
{showCollapseText && (
<Typography
variant={'smallText1'}
sx={{ color: 'grey.600', fontStyle: 'italic', mb: 1 }}
>
{`${value.length.toLocaleString()} items`}
<ExpandCollapseButton
isExpanded={expanded}
className="ExpandableTableData__expandButton"
onClick={() => setExpanded(!expanded)}
/>
</Typography>
)}
<Collapse in={expanded}>
{value.map((val: JSONPrimitiveType, index: number) => (
<Box display={'flex'} gap={0.5} mb={1} key={index}>
<Typography variant={'smallText1'}>
<JSONPrimitiveRenderer value={val} />
</Typography>
</Box>
))}
</Collapse>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { useState } from 'react'
import { Box, Collapse, Typography } from '@mui/material'
import ExpandCollapseButton from '../../ExpandCollapseButton'
import { JSONPrimitiveType } from './JSONRendererUtils'
import { JSONPrimitiveRenderer } from './JSONPrimitiveRenderer'

const OBJECT_DEFAULT_NUM_KEYS_TO_SHOW = 3
type JSONObjectKeyValuePairProps = {
keyName: string
value: JSONPrimitiveType
}

function JSONObjectKeyValuePair(props: JSONObjectKeyValuePairProps) {
const { keyName, value } = props

return (
<Box display={'flex'} mb={1}>
<Typography variant={'smallText1'}>
<JSONPrimitiveRenderer value={keyName} />
</Typography>
<Typography
variant={'smallText1'}
sx={{ color: 'grey.600', fontStyle: 'italic', px: 0.5 }}
>
<JSONPrimitiveRenderer value={value} />
</Typography>
</Box>
)
}

type JSONObjectRendererProps = {
value: Record<string, JSONPrimitiveType>
}

/**
* Handles rendering a JSON object where all values are JSON primitives in a Synapse table cell.
* @param props
* @constructor
*/
export function JSONObjectRenderer(props: JSONObjectRendererProps) {
const { value } = props
const numKeys = Object.keys(value).length
const isCollapsible = numKeys > OBJECT_DEFAULT_NUM_KEYS_TO_SHOW
const [expanded, setExpanded] = useState(false)

if (numKeys === 0) {
return (
<Typography variant={'smallText1'} className="SRC-inactive">
{'Empty object'}
</Typography>
)
}

return (
<>
{Object.entries(value)
// Render the first set of items unconditionally
.slice(0, OBJECT_DEFAULT_NUM_KEYS_TO_SHOW)
.map(([key, val]) => (
<JSONObjectKeyValuePair key={key} keyName={key} value={val} />
))}
{/* Render the rest of the items in a Collapse */}
{isCollapsible && (
<>
<Collapse in={expanded}>
{Object.entries(value)
.slice(OBJECT_DEFAULT_NUM_KEYS_TO_SHOW)
.map(([key, val]) => (
<JSONObjectKeyValuePair key={key} keyName={key} value={val} />
))}
</Collapse>
<Typography
variant={'smallText1'}
sx={{ mb: 0.5, color: 'grey.600', fontStyle: 'italic' }}
>
{expanded
? ''
: `${(
numKeys - OBJECT_DEFAULT_NUM_KEYS_TO_SHOW
).toLocaleString()} more`}
<ExpandCollapseButton
isExpanded={expanded}
className="ExpandableTableData__expandButton"
onClick={() => setExpanded(v => !v)}
/>
</Typography>
</>
)}
</>
)
}
Loading

0 comments on commit e1bfaaf

Please sign in to comment.