Skip to content

Commit b0bbfc9

Browse files
committed
Fix routing, hook up feature flags
1 parent 31ec932 commit b0bbfc9

File tree

10 files changed

+158
-39
lines changed

10 files changed

+158
-39
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use client';
2+
3+
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
4+
import { notFound } from 'next/navigation';
5+
import { use } from 'react';
6+
7+
import { getMEModel } from '@/api/entitycore/queries';
8+
import { resolveSimulationByCampaignId } from '@/entity-configuration/domain/simulation/small-microcircuit-simulation';
9+
import SimulationConfig from '@/features/small-microcircuit';
10+
import { keyBuilder } from '@/ui/use-query-keys/data';
11+
12+
import type { ServerSideComponentProp, WorkspaceContext } from '@/types/common';
13+
import type { WorkflowSimulatePanelKeys } from '@/ui/segments/workflows/simulate/single-neuron/shared/constant';
14+
import type { ExperimentStepKeys } from '@/ui/segments/workflows/simulate/single-neuron/shared/elements/menu';
15+
16+
export default function Page({
17+
searchParams,
18+
params: pathParams,
19+
}: ServerSideComponentProp<
20+
WorkspaceContext & { id: string },
21+
{
22+
step: ExperimentStepKeys;
23+
sessionId: string;
24+
panel: WorkflowSimulatePanelKeys;
25+
initialCampaignId: string;
26+
}
27+
>) {
28+
const queryParams = use(searchParams);
29+
const { initialCampaignId } = queryParams;
30+
const { virtualLabId, projectId, id: modelId } = use(pathParams);
31+
32+
let sessionId = queryParams?.sessionId;
33+
if (!sessionId) sessionId = crypto.randomUUID();
34+
35+
const { data: entity } = useSuspenseQuery({
36+
queryKey: [modelId],
37+
queryFn: () =>
38+
modelId ? getMEModel({ id: modelId, context: { virtualLabId, projectId } }) : null,
39+
});
40+
41+
const {
42+
data: campaignData,
43+
error,
44+
isLoading,
45+
} = useQuery({
46+
queryKey: keyBuilder.simCampaign({ entityId: initialCampaignId }),
47+
queryFn: async () => {
48+
if (!initialCampaignId) return null;
49+
return await resolveSimulationByCampaignId({
50+
id: initialCampaignId,
51+
context: { virtualLabId, projectId },
52+
});
53+
},
54+
});
55+
56+
if (error || !entity) {
57+
return notFound();
58+
}
59+
60+
if (
61+
!initialCampaignId ||
62+
(initialCampaignId && !isLoading && campaignData && campaignData.config.form)
63+
) {
64+
return (
65+
<div className="border-neutral-2 ml-2 h-full rounded-2xl border pt-3">
66+
<SimulationConfig
67+
modelId={entity.id}
68+
virtualLabId={virtualLabId}
69+
projectId={projectId}
70+
initialConfig={campaignData?.config.form}
71+
className="px-10 pt-2"
72+
/>
73+
</div>
74+
);
75+
}
76+
}

src/entity-configuration/definitions/view-defs/experiment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const ViewsDefinition: { [key: string]: ViewDefinitionConfig } = {
9292
],
9393
},
9494
[ExtendedEntitiesTypeDict.MemodelCircuitSimulation]: {
95-
title: 'ME-model circuit simulation',
95+
title: 'Single neuron [circuit] simulation',
9696
group: DataTypeGroup.SimulationData,
9797
name: EntitySlug.MEModelCircuitSimulation,
9898
curated: false,

src/entity-configuration/definitions/view-defs/simulation/memodel-circuit-simulation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { EntitySlug } from '@/entity-configuration/domain/slug';
66
import { EntityCoreFields } from '@/entity-configuration/definitions/fields-defs/enums';
77

88
export const viewDefForMEModelCircuitSimulation: ViewDefinitionConfig = {
9-
title: 'ME-model circuit simulation',
9+
title: 'Single neuron [circuit] simulation',
1010
group: DataTypeGroup.SimulationData,
1111
name: EntitySlug.MEModelCircuitSimulation,
1212
curated: false,

src/entity-configuration/domain/simulation/memodel-circuit-simulation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export async function resolveSimulationByCampaignId({
152152

153153
export const MEModelCircuitSimulation: EntityCoreTypeConfig<ICircuitSimulationCampaign> = {
154154
group: EntityTypeGroup.Simulations,
155-
title: 'ME-model circuit simulation',
155+
title: 'Single neuron [circuit] simulation',
156156
extendedType: ExtendedEntitiesTypeDict.MemodelCircuitSimulation,
157157
type: EntityTypeDict.SimulationCampaign,
158158
slug: EntitySlug.MEModelCircuitSimulation,

src/features/feature-flags/flags.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { env } from '@/env';
2-
import { FlagDefinition } from './config';
2+
import { FlagDefinition } from '@/features/feature-flags/config';
33

4-
export const flags = [
5-
{
6-
key: 'meModelObiOneSim',
7-
defaultValue: false,
8-
description: 'Enable new ME-model simulation flow with OBI-ONE integration',
9-
visible: ['local', 'development'].includes(env.NEXT_PUBLIC_DEPLOYMENT_ENV),
10-
},
11-
] satisfies FlagDefinition[];
4+
export const inifiedSingleNeuronSimulationFlowFlag = {
5+
key: 'unifiedSingleNeuronSimulationFlow',
6+
defaultValue: false,
7+
description: 'Enable new single neuron unified (circuit) simulation flow with OBI-ONE',
8+
visible: ['local', 'development'].includes(env.NEXT_PUBLIC_DEPLOYMENT_ENV),
9+
} satisfies FlagDefinition;
10+
11+
export const flags = [inifiedSingleNeuronSimulationFlowFlag] as const satisfies FlagDefinition[];
12+
13+
export type FlagKey = (typeof flags)[number]['key'];
1214

1315
export const hasVisibleFlags = flags.some((flag) => flag.visible);
1416

src/ui/segments/mini-detail-view/index.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ export function MiniDetailView<T extends EntityCoreObjectTypes>({
196196
<ExploreActions record={record} dataType={dataType} />
197197
))
198198
.with({ section: WorkspaceSection.SimulateWorkflow }, () => (
199-
<WorkflowSimulateActions record={record} />
199+
<WorkflowSimulateActions record={record} dataType={dataType} />
200200
))
201201
.with({ section: WorkspaceSection.BuildWorkflow }, () => (
202202
<WorkflowBuildActions record={record} />
@@ -446,7 +446,13 @@ function ExploreActions<T extends EntityCoreObjectTypes>({
446446
);
447447
}
448448

449-
function WorkflowSimulateActions<T extends EntityCoreObjectTypes>({ record }: { record: T }) {
449+
function WorkflowSimulateActions<T extends EntityCoreObjectTypes>({
450+
record,
451+
dataType,
452+
}: {
453+
record: T;
454+
dataType?: TExtendedEntitiesTypeDict;
455+
}) {
450456
const { virtualLabId, projectId } = useWorkspace();
451457

452458
return (
@@ -473,7 +479,8 @@ function WorkflowSimulateActions<T extends EntityCoreObjectTypes>({ record }: {
473479
>
474480
<Link
475481
href={{
476-
pathname: `${ROOT_ROUTE}/${virtualLabId}/${projectId}/workflows/simulate/configure/${kebabCase(record.type)}/${record.id}`,
482+
// pathname: `${ROOT_ROUTE}/${virtualLabId}/${projectId}/workflows/simulate/configure/${kebabCase(record.type)}/${record.id}`,
483+
pathname: `${ROOT_ROUTE}/${virtualLabId}/${projectId}/workflows/simulate/configure/${kebabCase(dataType)}/${record.id}`,
477484
query: {
478485
sessionId: crypto.randomUUID(),
479486
[PanelQueryParam]: WorkflowSimulatePanels.Configuration,

src/ui/segments/workflows/elements/browse-header.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { useWorkspace } from '@/ui/hooks/use-workspace';
2020
import type { TExtendedEntitiesTypeDict } from '@/api/entitycore/types/extended-entity-type';
2121
import type { TActivityValue } from '@/ui/segments/workflows/elements/helpers';
2222
import type { KebabCase } from '@/utils/type';
23+
import { useFlags } from '@/features/feature-flags';
2324

2425
const WorkflowScope = {
2526
Public: 'public',
@@ -102,10 +103,14 @@ export function ActivityAndTypeSelectors({
102103
onEntityTypeChange,
103104
onNavigate,
104105
}: WorkflowMenuProps) {
106+
const featureFlags = useFlags();
107+
105108
const handleActivitySelect = (v: TActivityValue | undefined) => {
106109
onActivityChange(v);
107110
if (v) {
108-
const type = getDropdownOptionsByCategory(v).enabledOptions.at(0)?.options.at(0)?.value;
111+
const type = getDropdownOptionsByCategory(v, featureFlags)
112+
.enabledOptions.at(0)
113+
?.options.at(0)?.value;
109114
onEntityTypeChange(type);
110115
onNavigate?.(type);
111116
}

src/ui/segments/workflows/elements/helpers.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import get from 'es-toolkit/compat/get';
77
import { ExtendedEntitiesTypeDict } from '@/api/entitycore/types/extended-entity-type';
88

99
import type { TExtendedEntitiesTypeDict } from '@/api/entitycore/types/extended-entity-type';
10+
import {
11+
FeatureFlags,
12+
FlagKey,
13+
inifiedSingleNeuronSimulationFlowFlag,
14+
} from '@/features/feature-flags/flags';
1015

1116
export const WorkflowSessionIdSearchParam = 'sessionId';
1217
export const EntityScopeDict = {
@@ -56,6 +61,7 @@ export const EntityWorkflowConfiguration: Partial<
5661
group: TEntityScopeValue;
5762
label: string;
5863
properties: Partial<Record<TActivityValue, EntityTypeProperties>>;
64+
requiredFeatures?: Array<FlagKey>;
5965
}
6066
>
6167
> = {
@@ -117,7 +123,8 @@ export const EntityWorkflowConfiguration: Partial<
117123
},
118124
[ExtendedEntitiesTypeDict.MemodelCircuit]: {
119125
group: EntityScopeDict.Cellular,
120-
label: 'ME-model circuit',
126+
label: 'Single neuron [circuit]',
127+
requiredFeatures: [inifiedSingleNeuronSimulationFlowFlag.key],
121128
properties: {
122129
build: {
123130
disabled: true,
@@ -253,12 +260,19 @@ export type TEntityDropdownOptionsGrouped = Array<{
253260
options: Array<EntityDropdownOption>;
254261
}>;
255262

256-
export function getDropdownOptionsByCategory(category: TActivityValue): {
263+
export function getDropdownOptionsByCategory(
264+
category: TActivityValue,
265+
featureFlags?: FeatureFlags
266+
): {
257267
allOptions: Array<EntityTypeGroupedOptions>;
258268
enabledOptions: Array<EntityTypeGroupedOptions>;
259269
} {
260270
const options = Object.values(EntityWorkflowConfiguration)
261271
.filter((config): config is NonNullable<typeof config> => config !== undefined)
272+
.filter(
273+
(config) =>
274+
!config.requiredFeatures || config.requiredFeatures.every((flag) => featureFlags?.[flag])
275+
)
262276
.map((config) => ({
263277
group: config.group,
264278
label: config.label,
@@ -300,9 +314,16 @@ export function getDropdownOptionsByCategory(category: TActivityValue): {
300314
};
301315
}
302316

303-
export function getAllOptionsOrdered(category: TActivityValue): Array<EntityTypeOption> {
317+
export function getAllOptionsOrdered(
318+
category: TActivityValue,
319+
featureFlags: FeatureFlags
320+
): Array<EntityTypeOption> {
304321
const options = Object.values(EntityWorkflowConfiguration)
305322
.filter((config): config is NonNullable<typeof config> => config !== undefined)
323+
.filter(
324+
(config) =>
325+
!config.requiredFeatures || config.requiredFeatures.every((flag) => featureFlags?.[flag])
326+
)
306327
.map((config) => ({
307328
group: config.group,
308329
label: config.label,

src/ui/segments/workflows/elements/selectors.tsx

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616

1717
import type { TExtendedEntitiesTypeDict } from '@/api/entitycore/types/extended-entity-type';
1818
import type { TActivityValue } from '@/ui/segments/workflows/elements/helpers';
19+
import { useFlags } from '@/features/feature-flags';
1920

2021
export function EntityTypeSelectScrollable({
2122
value,
@@ -26,6 +27,8 @@ export function EntityTypeSelectScrollable({
2627
value: TExtendedEntitiesTypeDict | undefined;
2728
onSelect: (v: TExtendedEntitiesTypeDict | undefined) => void;
2829
}) {
30+
const featureFlags = useFlags();
31+
2932
const breakpoint = useDefaultBreakpoint();
3033
if (!category) return null;
3134
return (
@@ -45,23 +48,25 @@ export function EntityTypeSelectScrollable({
4548
side="bottom"
4649
sideOffset={3}
4750
>
48-
{getDropdownOptionsByCategory(category).enabledOptions.map(({ group, options }) => (
49-
<SelectGroup key={`entity-type-group-${group}`}>
50-
<SelectLabel className="text-neutral-3 text-base">{group}</SelectLabel>
51-
{options.map(({ label, value: _value }) => (
52-
<SelectItem
53-
key={`entity-type-${_value}`}
54-
value={_value!}
55-
className={cn(
56-
'text-primary-9 text-lg font-bold',
57-
'data-[highlighted]:text-primary-7!'
58-
)}
59-
>
60-
{label}
61-
</SelectItem>
62-
))}
63-
</SelectGroup>
64-
))}
51+
{getDropdownOptionsByCategory(category, featureFlags).enabledOptions.map(
52+
({ group, options }) => (
53+
<SelectGroup key={`entity-type-group-${group}`}>
54+
<SelectLabel className="text-neutral-3 text-base">{group}</SelectLabel>
55+
{options.map(({ label, value: _value }) => (
56+
<SelectItem
57+
key={`entity-type-${_value}`}
58+
value={_value!}
59+
className={cn(
60+
'text-primary-9 text-lg font-bold',
61+
'data-[highlighted]:text-primary-7!'
62+
)}
63+
>
64+
{label}
65+
</SelectItem>
66+
))}
67+
</SelectGroup>
68+
)
69+
)}
6570
</SelectContent>
6671
</Select>
6772
);

src/ui/segments/workflows/elements/types-menu.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures';
22

3-
import { getAllOptionsOrdered } from '@/ui/segments/workflows/elements/helpers';
4-
import { CarouselButtons } from '@/ui/segments/workflows/elements/carousel-buttons';
3+
import { useFlags } from '@/features/feature-flags';
54
import { Carousel, CarouselContent, CarouselItem } from '@/ui/molecules/carousel';
5+
import { CarouselButtons } from '@/ui/segments/workflows/elements/carousel-buttons';
6+
import { getAllOptionsOrdered } from '@/ui/segments/workflows/elements/helpers';
67
import { MenuItem } from '@/ui/segments/workflows/elements/menu-item';
78

89
import type { TExtendedEntitiesTypeDict } from '@/api/entitycore/types/extended-entity-type';
@@ -17,6 +18,8 @@ export function TypesMenu({
1718
category: TActivityValue | undefined;
1819
onItemClick: (v: TExtendedEntitiesTypeDict | undefined) => void;
1920
}) {
21+
const featureFlags = useFlags();
22+
2023
if (!category) return null;
2124
return (
2225
<Carousel
@@ -33,7 +36,7 @@ export function TypesMenu({
3336
</div>
3437
</div>
3538
<CarouselContent className="items-stretch">
36-
{getAllOptionsOrdered(category).map(({ value, disabled, group, label }) => (
39+
{getAllOptionsOrdered(category, featureFlags).map(({ value, disabled, group, label }) => (
3740
<CarouselItem
3841
key={`category-selector-${value}`}
3942
className="w-max basis-1/2 py-2 md:basis-1/3! lg:basis-1/5! 2xl:basis-1/6!"

0 commit comments

Comments
 (0)