Skip to content

Commit

Permalink
Release 7.0.1 (#142)
Browse files Browse the repository at this point in the history
## What's changed

* feat: add get_default_postgres_url utility
(#136)
* feat: debug log for access_token in oauth security store
(#139)
* Fix: tooltip bug in violin plot
(#140)
* Handle null value only columns in raincloud plot
(#130)
  • Loading branch information
puehringer authored Jan 10, 2024
2 parents 7682cd9 + a2d31c1 commit 9167484
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 77 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "visyn_core",
"description": "Core repository for datavisyn applications.",
"version": "7.0.0",
"version": "7.0.1",
"author": {
"name": "datavisyn GmbH",
"email": "[email protected]",
Expand Down
137 changes: 73 additions & 64 deletions src/vis/raincloud/Raincloud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Circle } from './rain/Circle';
import { DotPlot } from './rain/DotPlot';
import { StripPlot } from './rain/StripPlot';
import { WheatPlot } from './rain/WheatPlot';
import { InvalidCols } from '../general/InvalidCols';

const margin = {
top: 0,
Expand Down Expand Up @@ -103,6 +104,8 @@ export function Raincloud({
.rollup({ values: op.mean('values'), ids: op.array_agg('ids') });
}, [baseTable]);

const hasData = column.resolvedValues.filter((row) => row.val !== null).length > 0;

return (
<Box ref={ref} style={{ maxWidth: '100%', maxHeight: '100%', position: 'relative', overflow: 'hidden' }}>
<Container
Expand All @@ -120,70 +123,76 @@ export function Raincloud({
<text textAnchor="middle" dominantBaseline="middle" x={width / 2} y={15}>
{column.info.name}
</text>
<g>
{config.cloudType === ECloudType.HEATMAP ? (
<Heatmap width={width} height={height / 2} config={config} numCol={column} />
) : config.cloudType === ECloudType.HISTOGRAM ? (
<Histogram width={width} height={height / 2} config={config} numCol={column} />
) : (
<SplitViolin width={width} height={height / 2} config={config} numCol={column} />
)}

{config.rainType === ERainType.DOTPLOT ? (
<DotPlot
yPos={height / 2}
width={width}
height={height}
config={config}
numCol={column}
circleCallback={circlesCallback}
baseTable={config.aggregateRain || column.resolvedValues.length > MAX_NON_AGGREGATED_COUNT ? aggregatedTable : baseTable}
/>
) : config.rainType === ERainType.BEESWARM ? (
<BeeSwarm
baseTable={config.aggregateRain || column.resolvedValues.length > MAX_NON_AGGREGATED_COUNT ? aggregatedTable : baseTable}
yPos={height / 2}
width={width}
height={height / 2}
config={config}
numCol={column}
circleCallback={circlesCallback}
/>
) : config.rainType === ERainType.WHEATPLOT ? (
<WheatPlot
yPos={height / 2}
width={width}
height={height / 2}
config={config}
numCol={column}
circleCallback={circlesCallback}
baseTable={config.aggregateRain || column.resolvedValues.length > MAX_NON_AGGREGATED_COUNT ? aggregatedTable : baseTable}
/>
) : config.rainType === ERainType.STRIPPLOT ? (
<StripPlot
yPos={height / 2}
width={width}
height={height / 2}
config={config}
numCol={column}
circleCallback={circlesCallback}
baseTable={config.aggregateRain || column.resolvedValues.length > MAX_NON_AGGREGATED_COUNT ? aggregatedTable : baseTable}
/>
) : null}
{circlesRendered}
{config.lightningType === ELightningType.MEAN_AND_DEV ? (
<MeanAndInterval yPos={height / 2} width={width} height={height} config={config} numCol={column} baseTable={baseTable} />
) : config.lightningType === ELightningType.MEAN ? (
<Mean yPos={height / 2} width={width} height={height} config={config} numCol={column} baseTable={baseTable} />
) : config.lightningType === ELightningType.MEDIAN_AND_DEV ? (
<MedianAndInterval yPos={height / 2} width={width} height={height} config={config} numCol={column} baseTable={baseTable} />
) : config.lightningType === ELightningType.BOXPLOT ? (
<Boxplot yPos={height / 2} width={width} height={height} config={config} numCol={column} baseTable={baseTable} />
) : null}

<XAxis xScale={xScale} vertPosition={height / 2} yRange={null} />
<Brush y={height / 2 - 20} x={margin.left} height={40} width={width - margin.left} onBrush={brushCallback} id={column.info.id} />
</g>
{hasData ? (
<g>
{config.cloudType === ECloudType.HEATMAP ? (
<Heatmap width={width} height={height / 2} config={config} numCol={column} />
) : config.cloudType === ECloudType.HISTOGRAM ? (
<Histogram width={width} height={height / 2} config={config} numCol={column} />
) : (
<SplitViolin width={width} height={height / 2} config={config} numCol={column} />
)}

{config.rainType === ERainType.DOTPLOT ? (
<DotPlot
yPos={height / 2}
width={width}
height={height}
config={config}
numCol={column}
circleCallback={circlesCallback}
baseTable={config.aggregateRain || column.resolvedValues.length > MAX_NON_AGGREGATED_COUNT ? aggregatedTable : baseTable}
/>
) : config.rainType === ERainType.BEESWARM ? (
<BeeSwarm
baseTable={config.aggregateRain || column.resolvedValues.length > MAX_NON_AGGREGATED_COUNT ? aggregatedTable : baseTable}
yPos={height / 2}
width={width}
height={height / 2}
config={config}
numCol={column}
circleCallback={circlesCallback}
/>
) : config.rainType === ERainType.WHEATPLOT ? (
<WheatPlot
yPos={height / 2}
width={width}
height={height / 2}
config={config}
numCol={column}
circleCallback={circlesCallback}
baseTable={config.aggregateRain || column.resolvedValues.length > MAX_NON_AGGREGATED_COUNT ? aggregatedTable : baseTable}
/>
) : config.rainType === ERainType.STRIPPLOT ? (
<StripPlot
yPos={height / 2}
width={width}
height={height / 2}
config={config}
numCol={column}
circleCallback={circlesCallback}
baseTable={config.aggregateRain || column.resolvedValues.length > MAX_NON_AGGREGATED_COUNT ? aggregatedTable : baseTable}
/>
) : null}
{circlesRendered}
{config.lightningType === ELightningType.MEAN_AND_DEV ? (
<MeanAndInterval yPos={height / 2} width={width} height={height} config={config} numCol={column} baseTable={baseTable} />
) : config.lightningType === ELightningType.MEAN ? (
<Mean yPos={height / 2} width={width} height={height} config={config} numCol={column} baseTable={baseTable} />
) : config.lightningType === ELightningType.MEDIAN_AND_DEV ? (
<MedianAndInterval yPos={height / 2} width={width} height={height} config={config} numCol={column} baseTable={baseTable} />
) : config.lightningType === ELightningType.BOXPLOT ? (
<Boxplot yPos={height / 2} width={width} height={height} config={config} numCol={column} baseTable={baseTable} />
) : null}

<XAxis xScale={xScale} vertPosition={height / 2} yRange={null} />
<Brush y={height / 2 - 20} x={margin.left} height={40} width={width - margin.left} onBrush={brushCallback} id={column.info.id} />
</g>
) : (
<foreignObject width="100%" height="100%">
<InvalidCols headerMessage="Invalid data" bodyMessage="Could not render plot due to all values being null." />
</foreignObject>
)}
</g>
) : null}
</svg>
Expand Down
46 changes: 35 additions & 11 deletions src/vis/violin/ViolinVis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,40 @@ export function ViolinVis({ config, columns, scales, dimensions, selectedList, s

const [layout, setLayout] = useState<Partial<Plotly.Layout>>(null);

const onClick = (e: Readonly<PlotlyTypes.PlotSelectionEvent> | null) => {
// Filter out null values from traces as null values cause the tooltip to not show up
const filteredTraces = useMemo(() => {
if (!traces) return null;
const indexWithNull = traces.plots?.map(
(plot) => (plot?.data.y as PlotlyTypes.Datum[])?.reduce((acc: number[], curr, i) => (curr === null ? [...acc, i] : acc), []) as number[],
);
const filtered = {
...traces,
plots: traces?.plots?.map((p, p_index) => {
return {
...p,
data: {
...p.data,
y: (p.data?.y as PlotlyTypes.Datum[])?.filter((v, i) => !indexWithNull[p_index].includes(i)),
x: (p.data?.x as PlotlyTypes.Datum[])?.filter((v, i) => !indexWithNull[p_index].includes(i)),
ids: p.data?.ids?.filter((v, i) => !indexWithNull[p_index].includes(i)),
transforms: p.data?.transforms?.map(
(t) => (t.groups as unknown[])?.filter((v, i) => !indexWithNull[p_index].includes(i)) as Partial<PlotlyTypes.Transform>,
),
},
};
}),
};
return filtered;
}, [traces]);

const onClick = (e: (Readonly<PlotlyTypes.PlotSelectionEvent> & { event: MouseEvent }) | null) => {
if (!e || !e.points || !e.points[0]) {
selectionCallback([]);
return;
}

// @ts-ignore
const shiftPressed = e.event.shiftKey;
// @ts-ignore
const eventIds = e.points[0]?.fullData.ids;
const eventIds = (e.points[0] as Readonly<PlotlyTypes.PlotSelectionEvent>['points'][number] & { fullData: { ids: string[] } })?.fullData.ids;

// Multiselect enabled
if (shiftPressed) {
Expand Down Expand Up @@ -78,7 +102,7 @@ export function ViolinVis({ config, columns, scales, dimensions, selectedList, s
}, [clearTimeoutValue]);

useEffect(() => {
if (!traces) {
if (!filteredTraces) {
return;
}

Expand All @@ -99,12 +123,12 @@ export function ViolinVis({ config, columns, scales, dimensions, selectedList, s
},
clickmode: 'event+select',
autosize: true,
grid: { rows: traces.rows, columns: traces.cols, xgap: 0.3, pattern: 'independent' },
grid: { rows: filteredTraces.rows, columns: filteredTraces.cols, xgap: 0.3, pattern: 'independent' },
shapes: [],
};

setLayout((prev) => ({ ...prev, ...beautifyLayout(traces, innerLayout, prev, true) }));
}, [traces]);
setLayout((prev) => ({ ...prev, ...beautifyLayout(filteredTraces, innerLayout, prev, true) }));
}, [filteredTraces]);

return (
<Stack
Expand All @@ -120,10 +144,10 @@ export function ViolinVis({ config, columns, scales, dimensions, selectedList, s
},
}}
>
{traceStatus === 'success' && layout && traces?.plots.length > 0 ? (
{traceStatus === 'success' && layout && filteredTraces?.plots.length > 0 ? (
<PlotlyComponent
divId={`plotlyDiv${id}`}
data={[...traces.plots.map((p) => p.data), ...traces.legendPlots.map((p) => p.data)]}
data={[...filteredTraces.plots.map((p) => p.data), ...filteredTraces.legendPlots.map((p) => p.data)]}
layout={layout}
config={{ responsive: true, displayModeBar: false }}
useResizeHandler
Expand All @@ -139,7 +163,7 @@ export function ViolinVis({ config, columns, scales, dimensions, selectedList, s
}}
/>
) : traceStatus !== 'pending' && traceStatus !== 'idle' && layout ? (
<InvalidCols headerMessage={traces?.errorMessageHeader} bodyMessage={traceError?.message || traces?.errorMessage} />
<InvalidCols headerMessage={filteredTraces?.errorMessageHeader} bodyMessage={traceError?.message || filteredTraces?.errorMessage} />
) : null}
</Stack>
);
Expand Down
2 changes: 1 addition & 1 deletion visyn_core/security/store/oauth2_security_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def load_from_request(self, req: Request):
# Get token data from header
access_token = req.headers.get(token_field)
if access_token:
# Try to decode the oidc data jwt
_log.debug(f"Try to decode the oidc data jwt with access token: {access_token}")
user = jwt.decode(access_token, options={"verify_signature": False})

# Go through all the fields we want to check for the user id
Expand Down
4 changes: 4 additions & 0 deletions visyn_core/settings/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ class VisynCoreSettings(BaseModel):

class GlobalSettings(BaseSettings):
env: Literal["development", "production"] = "production"
ci: bool = False
"""
Set to true in CI environments like Github Actions.
"""
secret_key: str = "VERY_SECRET_STUFF_T0IB84wlQrdMH8RVT28w"

# JWT options mostly inspired by flask-jwt-extended: https://flask-jwt-extended.readthedocs.io/en/stable/options/#general-options
Expand Down
15 changes: 15 additions & 0 deletions visyn_core/settings/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,18 @@ def load_config_file(path: str) -> dict[str, Any]:
"""
with codecs.open(path, "r", "utf-8") as fi:
return jsoncfg.loads(fi.read()) or {}


def get_default_postgres_url(
*,
user: str = "admin",
password: str = "admin",
host: str | None = os.getenv("POSTGRES_HOSTNAME"),
host_fallback: str = "localhost",
port: int = 5432,
database: str = "db",
) -> str:
"""
Returns a default postgres url, including the default values for `user`, `password`, `host`, `port` and `database`.
"""
return f"postgresql://{user}:{password}@{host or host_fallback}:{port}/{database}"

0 comments on commit 9167484

Please sign in to comment.