Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# AGENTS.md

Instructions for automated coding agents working in this repository.

## Scope

These instructions apply to the whole repository.

## Project Overview

`bi-web` is a Vue 2.7 frontend for Breeding Insight. It uses Vue CLI 4,
TypeScript, Vuex, Vue Router, Buefy/Bulma, Vuelidate, CASL abilities, and an
axios wrapper for API calls.

Important directories:

- `src/views`: route-level pages.
- `src/components`: reusable Vue components and layout templates.
- `src/store`: Vuex root store and feature modules.
- `src/breeding-insight/model`: application domain models.
- `src/breeding-insight/dao`: raw API access using `src/util/api`.
- `src/breeding-insight/service`: business logic and response mapping.
- `src/breeding-insight/brapi/model`: generated BrAPI/OpenAPI models.
- `tests/unit`: Jest and Vue Test Utils unit tests.
- `tests/e2e`: Cypress end-to-end tests.
- `task`: Node task wrappers used by npm scripts.

## Setup

- Use npm and `package-lock.json`; do not introduce Yarn or pnpm.
- Use `npm install` for local setup, or `npm ci` for clean/install-in-CI style
runs.
- The Dockerfile uses Node 14 and npm 8.12.1. Avoid dependency or syntax changes
that require a newer runtime unless the runtime is intentionally updated.
- `npm run githooks` configures the repository's commit message hook.
- Local environment overrides belong in ignored `.env.local` or `.env.*.local`
files, not in tracked env files.

## Common Commands

- `npm run serve`: start the dev server. The default port is `8080`; `PORT`
overrides it.
- `npm run build`: production build. The task wrapper runs npm audit checks
before building.
- `npm run lint`: run Vue CLI ESLint.
- `npm run test:unit`: run existing Jest unit tests only when explicitly
requested.
- `npm run test:e2e`: run existing Cypress e2e tests only when explicitly
requested.
- `npm run test:accessibility`: run pa11y accessibility checks. This expects
the dev server to be running and `task/.pa11yTargets.json` to exist; use it
only when explicitly requested.

This repository does not add or maintain tests as part of normal feature or bug
work. Do not create new tests, update existing tests, add test-only selectors,
or spend time repairing unrelated test failures unless the user explicitly asks
for test work. Prefer `npm run lint`, `npm run build`, or a focused manual smoke
check for verification when practical.

## Coding Conventions

- Preserve the existing Apache 2.0 notice header when editing files that have
it. Add the same header to new `.ts`, `.js`, and `.vue` source files.
- Keep TypeScript strictness in mind. Prefer explicit domain models and local
service/DAO patterns over loose `any` unless surrounding code already forces
it.
- Use the `@/` alias for imports from `src` when consistent with nearby files.
- Vue components commonly use class-style components with
`vue-property-decorator`; match the surrounding component style.
- ESLint requires long-form Vue directives: use `v-bind:` and `v-on:` instead
of shorthand in templates.
- Keep API calls centralized through `src/util/api`. DAOs should make HTTP
requests, while services should handle application mapping and user-facing
error logic.
- `src/breeding-insight/brapi/model` files are generated by Swagger/OpenAPI.
Do not hand-edit them unless the task explicitly calls for it.
- Reuse existing layouts in `src/components/layouts` and visual conventions
from the authenticated `/style-guide` page. Prefer existing Buefy/Bulma
patterns and the configured Feather icon pack.

## Configuration Notes

`vue.config.js` derives frontend runtime values from environment variables:

- `VUE_APP_BI_API_ROOT` defaults to `http://localhost`.
- `VUE_APP_BI_API_V1_PATH` is computed as `${VUE_APP_BI_API_ROOT}/v1`.
- `VUE_APP_LOG_LEVEL` defaults to `error`.
- `VUE_APP_BRAPI_VENDOR_SUBMISSION_ENABLED` and
`VUE_APP_ALTERNATE_AUTHENTICATION_ENABLED` are enabled only when set to the
string `true`.

`.env.development` maps these values from shell environment variables such as
`API_BASE_URL`, `SANDBOX_MODE`, and `WEB_LOG_LEVEL`.

## Working Safely

- Check `git status --short` before editing and do not overwrite unrelated
local changes.
- Do not commit generated output, `node_modules`, `dist`, local env files, IDE
files, Cypress screenshots/videos, or task logs.
- Do not change `package-lock.json` unless dependency changes are intentional.
- Keep changes scoped to the requested behavior. Avoid broad refactors unless
they are required to make the requested change safely.
- If backend behavior is relevant, make the frontend assumption explicit in the
code review or final notes; this repository only contains the web client.
25 changes: 24 additions & 1 deletion src/breeding-insight/dao/GenoDAO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import * as api from '@/util/api';
import { BiResponse, Response } from '@/breeding-insight/model/BiResponse';
import { PaginationQuery } from '@/breeding-insight/model/PaginationQuery';
import { GenotypeImportFilters, GenotypeImportSort } from '@/breeding-insight/model/Sort';

export class GenoDAO {

Expand All @@ -42,4 +44,25 @@ export class GenoDAO {

return new BiResponse(data);
}
}

static async fetchGenotypeImports(
programId: string,
{page, pageSize}: PaginationQuery,
{field, order}: GenotypeImportSort,
filters: GenotypeImportFilters
): Promise<BiResponse> {
const {data} = await api.call({
url: `${process.env.VUE_APP_BI_API_V1_PATH}/programs/${programId}/geno/imports`,
method: 'get',
params: {
...filters,
page,
pageSize,
sortField: field,
sortOrder: order
}
}) as Response;

return new BiResponse(data);
}
}
41 changes: 41 additions & 0 deletions src/breeding-insight/model/GenotypeImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export class GenotypeImport {
sampleSubmissionId?: string;
projectNameForSampleSubmission?: string;
sampleSubmissionCreatedBy?: string;
genotypingFileName?: string;
genotypingImportDate?: string;
genotypingImportBy?: string;

constructor({
sampleSubmissionId,
projectNameForSampleSubmission,
sampleSubmissionCreatedBy,
genotypingFileName,
genotypingImportDate,
genotypingImportBy
}: GenotypeImport = {}) {
this.sampleSubmissionId = sampleSubmissionId;
this.projectNameForSampleSubmission = projectNameForSampleSubmission;
this.sampleSubmissionCreatedBy = sampleSubmissionCreatedBy;
this.genotypingFileName = genotypingFileName;
this.genotypingImportDate = genotypingImportDate;
this.genotypingImportBy = genotypingImportBy;
}
}
22 changes: 21 additions & 1 deletion src/breeding-insight/model/Sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,24 @@ export class GermplasmListSort {
this.field = field;
this.order = order;
}
}
}

export enum GenotypeImportSortField {
ProjectNameForSampleSubmission = 'projectNameForSampleSubmission',
SampleSubmissionCreatedBy = 'sampleSubmissionCreatedBy',
GenotypingFileName = 'genotypingFileName',
GenotypingImportDate = 'genotypingImportDate',
GenotypingImportBy = 'genotypingImportBy'
}

export type GenotypeImportFilters = Partial<Record<GenotypeImportSortField, string>>;

export class GenotypeImportSort {
field: GenotypeImportSortField;
order: SortOrder;

constructor(field: GenotypeImportSortField, order: SortOrder) {
this.field = field;
this.order = order;
}
}
29 changes: 27 additions & 2 deletions src/breeding-insight/service/GenoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
*/

import { GenoDAO } from '@/breeding-insight/dao/GenoDAO';
import { BiResponse } from '@/breeding-insight/model/BiResponse';
import { BiResponse, Metadata } from '@/breeding-insight/model/BiResponse';
import { ImportResponse } from '@/breeding-insight/model/import/ImportResponse';
import { GermplasmGenotype } from '@/breeding-insight/model/GermplasmGenotype';
import { GenotypeImport } from '@/breeding-insight/model/GenotypeImport';
import { PaginationQuery } from '@/breeding-insight/model/PaginationQuery';
import { GenotypeImportFilters, GenotypeImportSort } from '@/breeding-insight/model/Sort';

export class GenoService {

Expand Down Expand Up @@ -47,4 +50,26 @@ export class GenoService {

return resp.result as GermplasmGenotype;
}
}

static async fetchGenotypeImports(
programId: string,
paginationQuery: PaginationQuery,
sort: GenotypeImportSort,
filters: GenotypeImportFilters
): Promise<[GenotypeImport[], Metadata]> {
if (!programId) {
throw 'Program ID not provided';
}

const response = await GenoDAO.fetchGenotypeImports(programId, paginationQuery, sort, filters);
const responseData = response.result && response.result.data ? response.result.data : response.result;

if (!Array.isArray(responseData)) {
return [[], response.metadata];
}

const genotypeImports = responseData.map((record: GenotypeImport) => new GenotypeImport(record));

return [genotypeImports, response.metadata];
}
}
11 changes: 11 additions & 0 deletions src/components/layouts/UserSideBarLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@
Sample Management
</router-link>
</li>
<li
v-if="$ability.can('access', 'Genotyping')"
>
<router-link
v-bind:id="genotypingMenuId"
v-bind:to="{name: 'genotyping', params: {programId: activeProgram.id}}"
>
Genotyping
</router-link>
</li>
<!--
<li>
<a>Labels</a>
Expand Down Expand Up @@ -243,6 +253,7 @@
private homeMenuId: string = "usersidebarlayout-home-menu";
private importFileMenuId: string = "usersidebarlayout-import-file-menu";
private sampleMgmtMenuId: string = "usersidebarlayout-sample-management-menu";
private genotypingMenuId: string = "usersidebarlayout-genotyping-menu";
private ontologyMenuId: string = "usersidebarlayout-ontology-menu";
private programManagementMenuId: string = "usersidebarlayout-program-management-menu";
private jobManagementMenuId: string = "usersidebarlayout-job-management-menu";
Expand Down
4 changes: 2 additions & 2 deletions src/config/AppAbility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import {Ability, AbilityClass} from '@casl/ability';

type Actions = 'manage' | 'create' | 'read' | 'update' | 'delete' | 'archive' | 'access' | 'submit';
type Subjects = 'ProgramUser' | 'Location' | 'User' | 'AdminSection' | 'Trait' | 'Import' | 'ProgramConfiguration' | 'Submission'
| 'Experiment' | 'Germplasm' | 'Ontology' | 'SampleManagement' | 'ProgramAdministration' | 'JobManagement' | 'Collaborator' | 'BrAPI'
| 'Experiment' | 'Germplasm' | 'Ontology' | 'SampleManagement' | 'Genotyping' | 'ProgramAdministration' | 'JobManagement' | 'Collaborator' | 'BrAPI'
| 'SubEntityDataset' | 'List';

export type AppAbility = Ability<[Actions, Subjects]>;
export const AppAbility = Ability as AbilityClass<AppAbility>;
export const AppAbility = Ability as AbilityClass<AppAbility>;
5 changes: 4 additions & 1 deletion src/config/ability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const rolePermissions: Record<string, DefinePermissions> = {
can('access', 'Germplasm');
can('access', 'ProgramAdministration');
can('access', 'SampleManagement');
can('access', 'Genotyping');
can('access', 'BrAPI');
can('access', 'JobManagement');
},
Expand Down Expand Up @@ -57,6 +58,7 @@ const rolePermissions: Record<string, DefinePermissions> = {
can('access', 'Ontology');
can('access', 'Germplasm');
can('access', 'SampleManagement');
can('access', 'Genotyping');
can('delete', 'Submission');
can('access', 'ProgramAdministration');
can('access', 'BrAPI');
Expand All @@ -80,6 +82,7 @@ const rolePermissions: Record<string, DefinePermissions> = {
can('access', 'Ontology');
can('access', 'Germplasm');
can('access', 'SampleManagement');
can('access', 'Genotyping');
can('access', 'ProgramAdministration');
can('access', 'BrAPI');
can('access', 'JobManagement');
Expand Down Expand Up @@ -127,4 +130,4 @@ export function defineAbilityFor(user: User | undefined, program: Program | unde
}

return builder.build();
}
}
11 changes: 11 additions & 0 deletions src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import ImportGeno from "@/views/import/ImportGeno.vue";
import ImportSample from "@/views/import/ImportSample.vue";
import SampleManagement from '@/views/sample-mgmt/SampleManagement.vue';
import SubmissionDetails from '@/views/sample-mgmt/SubmissionDetails.vue';
import Genotyping from '@/views/genotyping/Genotyping.vue';

Vue.use(VueRouter);

Expand Down Expand Up @@ -466,6 +467,16 @@ const routes = [
component: SampleManagement,
beforeEnter: processProgramNavigation
},
{
path: '/programs/:programId/genotyping',
name: 'genotyping',
meta: {
title: 'Genotyping',
layout: layouts.userSideBar
},
component: Genotyping,
beforeEnter: processProgramNavigation
},
{
path: '/programs/:programId/sample-management/:submissionId',
name: 'submission-details',
Expand Down
Loading
Loading