diff --git a/.build-files/docker-compose-local.template.yml b/.build-files/docker-compose-local.template.yml index f651e5f5..bd699b5f 100644 --- a/.build-files/docker-compose-local.template.yml +++ b/.build-files/docker-compose-local.template.yml @@ -41,4 +41,4 @@ services: volumes: duckdb-data: - migrations_volume: \ No newline at end of file + migrations_volume: diff --git a/README.md b/README.md index 170cda62..8cd187cc 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,30 @@ In the root of the repository, run the following following command: python3 build.py --prepare-dev ``` +### Prepare Development Environment and Run Separately with Experiment Visibility Control + +Experiment visibility control allows users to control which experiments are visible to them. You specify which experiments are visible to users in the `aa_index.json` file. + +```bash +python3 build.py --prepare-dev --experiment-visibility-control +``` + +Experiment visibility control example: + +```json +{ + "experiments": [ + { + "filename": "Data Discovery - Automated Cell Lineage Tracking.json", + "visible-by-default": true + }, + "Quality Control - Cancer Triggering Microenvironments.json", + "Communication - Tumorigenic Cell States.json" + ] +} +``` + + This will generate the necessary environment file in the client directory while still running the rest of the container. Then, the development server can be started separately by the following: ```bash @@ -109,6 +133,7 @@ The build script will do its best to validate the config before starting the doc | -o, --overwrite | When set, any settings in your configuration file which are present as environment variables in the current session will be overwritten. | -o | | -s, --disable-spinner | When set, disables inline spinner | -s | | --prepare-dev | When set, will create the `.env` file based on the current configuration that is required to run a separate client development server. | --prepare-dev | +| --experiment-visibility-control | When set, will create the `.env` file based on the current configuration that is required to run a separate client development server. | --experiment-visibility-control| In the repository, you will see two docker directories: `docker` and `docker-local`. The main deployment will use the `docker` directory. The `docker-local` directory is a separate local version of Loon which we will discuss shortly. Below are some examples. diff --git a/apps/client/src/stores/dataStores/datasetSelectionUntrrackedStore.ts b/apps/client/src/stores/dataStores/datasetSelectionUntrrackedStore.ts index 58ee30c2..a3cbf797 100644 --- a/apps/client/src/stores/dataStores/datasetSelectionUntrrackedStore.ts +++ b/apps/client/src/stores/dataStores/datasetSelectionUntrrackedStore.ts @@ -1,6 +1,7 @@ import { ref, computed, watch } from 'vue'; import { defineStore } from 'pinia'; import { asyncComputed } from '@vueuse/core'; +import { useRoute } from 'vue-router'; import * as vg from '@uwdata/vgplot'; import { computedAsync } from '@vueuse/core'; @@ -44,7 +45,7 @@ export interface LocationMetadata { imageDataFilename?: string; segmentationsFolder?: string; tags?: Tags; - // name?: string; // user friendly name + name?: string; // user friendly name // condition?: string; // experimental condition // TODO: - does this need to be an array // plate?: string; // well?: string; @@ -95,8 +96,9 @@ export const useDatasetSelectionStore = defineStore( }) // Generate Experiment List + const route = useRoute(); const experimentFilenameList = asyncComputed(async () => { - if (configStore.serverUrl == null) return null; + if (configStore.serverUrl == null) return []; const fullURL = configStore.getFileUrl( configStore.entryPointFilename ); @@ -115,12 +117,12 @@ export const useDatasetSelectionStore = defineStore( `Could not access ${fullURL}. "${error.message}"` ); }); - if (response == null) return; + if (response == null) return []; if (!response.ok) { handleFetchEntryError( `Server Error. "${response.status}: ${response.statusText}"` ); - return; + return []; } fetchingEntryFile.value = false; serverUrlValid.value = true; @@ -136,7 +138,33 @@ export const useDatasetSelectionStore = defineStore( vg.coordinator().databaseConnector(connector); const data = await response.json(); - return data.experiments; + const rawExperiments: (string | { filename: string; 'visible-by-default'?: boolean })[] = data.experiments; + + // Normalize entries: extract filename from objects, pass strings through + const allFilenames = rawExperiments.map((entry) => + typeof entry === 'string' ? entry : entry.filename + ); + + // If experiment visibility control is not enabled, show all experiments + const visibilityControlEnabled = + import.meta.env.VITE_EXPERIMENT_VISIBILITY_CONTROL === 'true'; + if (!visibilityControlEnabled) { + return allFilenames; + } + + // If show-all-experiments URL param is present, show all + if ('show-all-experiments' in route.query) { + return allFilenames; + } + + // Filter to only visible-by-default experiments + return rawExperiments + .filter((entry) => + typeof entry === 'object' && entry['visible-by-default'] === true + ) + .map((entry) => + typeof entry === 'string' ? entry : entry.filename + ); }, [refreshTime.value]); // Sets location Metadata @@ -284,7 +312,7 @@ export const useDatasetSelectionStore = defineStore( .locationMetadataList) { if ( datasetSelectionTrrackedStore.selectedLocationIds[ - location.id + location.id ] ) { return location; diff --git a/build.py b/build.py index dc5b9ac0..9642ff11 100644 --- a/build.py +++ b/build.py @@ -81,7 +81,7 @@ def createComposeFile(local=False, nfs=False): shutil.copy(docker_compose_template, '.build-files/docker-compose.yml') -def createEnvFile(configFileName, envFileName, useDid=False): +def createEnvFile(configFileName, envFileName, useDid=False, experimentVisibilityControl=False): buildConfig = BuildConfig.BuildConfig(configFileName, envFileName) buildConfig.reportErrors() @@ -110,6 +110,7 @@ def createEnvFile(configFileName, envFileName, useDid=False): buildConfig.set('VITE_ENVIRONMENT', environment) buildConfig.set('VITE_SERVER_URL', f'{base_url}/data') + buildConfig.set('VITE_EXPERIMENT_VISIBILITY_CONTROL', str(experimentVisibilityControl).lower()) localPort1 = buildConfig.get("generalSettings.local_port_1") buildConfig.set("LOCAL_PORT_1", localPort1) @@ -390,14 +391,16 @@ def cleanup_and_exit(signal=None, frame=None): sys.exit(0) -def prepare_dev(buildConfig): +def prepare_dev(buildConfig, experimentVisibilityControl=False): vite_server_url = f"{buildConfig.get('generalSettings.baseUrl')}/data" vite_use_http = f"{buildConfig.get('generalSettings.useHttp')}" vite_environment = f"{buildConfig.get('generalSettings.environment')}" + vite_experiment_visibility_control = str(experimentVisibilityControl).lower() outString = f"VITE_SERVER_URL={vite_server_url}\n" \ f"VITE_USE_HTTP={vite_use_http}\n" \ - f"VITE_ENVIRONMENT={vite_environment}\n" + f"VITE_ENVIRONMENT={vite_environment}\n" \ + f"VITE_EXPERIMENT_VISIBILITY_CONTROL={vite_experiment_visibility_control}\n" fullEnvFileName = 'apps/client/.env' with open(fullEnvFileName, 'w') as outF: @@ -445,6 +448,10 @@ def spinner(msg): help="Generates .env file for client environment.") parser.add_argument("-i", "--use-did", action="store_true", required=False, help="Flag to specify that this build script is running in a DiD setup.") + parser.add_argument("--experiment-visibility-control", action="store_true", required=False, + help="Enable experiment visibility control. Only experiments with " + "'visible-by-default' in aa_index.json will be shown unless " + "'show-all-experiments' URL param is used.") args = parser.parse_args() @@ -464,7 +471,7 @@ def spinner(msg): print('No client .env file found.') pass - buildConfig = createEnvFile(config_file_name, args.env_file, args.use_did) + buildConfig = createEnvFile(config_file_name, args.env_file, args.use_did, args.experiment_visibility_control) use_http = buildConfig.get('generalSettings.useHttp') if use_http: @@ -513,7 +520,7 @@ def spinner(msg): follow_all_logs(logs_path, services, args.verbose, args.detached) if args.prepare_dev: - prepare_dev(buildConfig) + prepare_dev(buildConfig, args.experiment_visibility_control) check_containers_status(services, args.detached) else: @@ -522,5 +529,5 @@ def spinner(msg): if args.overwrite: config_file_name = overwrite_config(config_file_name) - buildConfig = createEnvFile(config_file_name, args.env_file, args.use_did) + buildConfig = createEnvFile(config_file_name, args.env_file, args.use_did, args.experiment_visibility_control) createComposeFile(local=buildConfig.local, nfs=buildConfig.nfs)