Skip to content

Feature #134 closing studies based on selection#135

Open
junaidferoz wants to merge 2 commits intodevfrom
feat-134-close_all_studies_options
Open

Feature #134 closing studies based on selection#135
junaidferoz wants to merge 2 commits intodevfrom
feat-134-close_all_studies_options

Conversation

@junaidferoz
Copy link
Copy Markdown
Collaborator

@junaidferoz junaidferoz commented Mar 26, 2026

Selective Study Closing with Workflow and User Filters

Previously, users could only close all studies at once. This feature adds the ability to selectively close studies by filtering them based on workflow type and/or user, reducing the risk of unintentionally closing studies and providing finer-grained control over bulk operations.

New User Features

  • Workflow filter: Select a specific workflow type when closing studies, or choose "All workflows" to close studies across all workflows.
  • User filter: Optionally filter studies by the user who owns them, with a default "Any user" option for unfiltered behavior.
  • Scoped confirmation: The confirmation dialog now displays which workflow and user filters are applied, along with an approximate count of studies to be closed.

New Dev Features

  • Extended studyCloseBulk handler: Backend now accepts optional workflowId and userId parameters to filter study lists before closing.
  • Server-side validation: All filtering is enforced on the backend to ensure malicious client requests cannot close out-of-scope studies.

Improvements

  • Filtered progress reporting: Progress indicators now reflect only the studies being closed within the selected scope, providing accurate feedback.
  • Enhanced safety: Users can preview exactly which studies will be affected before confirming the bulk close operation

@junaidferoz junaidferoz self-assigned this Mar 26, 2026
@junaidferoz junaidferoz changed the title Dashboard: scoped bulk close for studies (workflow / owner / guest role) [FEATURE] it would be good to be able to select types of studies, to be able to not close all but still as a bulk request Mar 26, 2026
@junaidferoz junaidferoz added the enhancement New feature or request label Mar 26, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refines “Bulk Close Studies” so operators can scope bulk-closing to a subset of open studies (by workflow / owner / guest-role), preview the matching count in the UI, and have the backend close exactly the study IDs computed by the client (with a strict mode to avoid falling back to “close everything”).

Changes:

  • Updated the bulk-close modal UI to support workflow/user scoping, show a live match count, and send an explicit study id list (studyIds + studyIdsJson) with bulkCloseUseIdList: true.
  • Updated the backend studyCloseBulk handler to resolve id lists robustly (array/object/JSON) and return closedCount.
  • Adjusted supporting config/ops files (sequelize CLI env loading; additional gitignore entries).

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
frontend/src/components/dashboard/study/BulkCloseModal.vue Adds workflow/user/guest scoping UI, match count, and emits bulk close with explicit study id list.
frontend/src/components/dashboard/Study.vue Updates bulk-close button label/tooltip.
backend/webserver/sockets/study.js Adds id-list resolution + strict mode; filters/loads studies and returns closedCount.
backend/db/.sequelizerc Loads repo-root .env (and .env.$ENV) for sequelize-cli runs.
.gitignore Ignores a specific root artifact filename and root logs/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +193 to +196
const matches = this.$store.getters["table/user_role_matching/getFiltered"](
(m) => !m.deleted && Number(m.userId) === uid && m.userRoleId === role.id
);
return matches.length > 0;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

studyUserHasRole is called from matchingOpenStudies() for every open study, and each call performs a getFiltered scan over the entire user_role_matching table. That makes filtering O(#studies × #matchings) and can become noticeably slow on larger projects. Consider precomputing a Set of userIds for the selected role once (computed property) and doing O(1) membership checks while filtering studies.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should do that, work with computed here

* @returns {Promise<{closedCount: number}>} Number of studies that were closed (updated).
*/
async closeBulk(data, options) {

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

studyCloseBulk currently performs a destructive operation (closing studies) without any server-side authorization check. Since any socket client can emit this event directly, this should enforce a permission (e.g., await this.isAdmin() or await this.hasAccess("frontend.dashboard.studies.closeAllStudies")) before proceeding, otherwise users can bypass the UI restriction and close studies they shouldn't.

Suggested change
// Enforce server-side authorization for bulk-closing studies.
if (!(await this.isAdmin()) && !(await this.hasAccess("frontend.dashboard.studies.closeAllStudies"))) {
throw new Error("No permission to bulk close studies");
}

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +59
if (!Number.isFinite(id) || seen.has(id)) {
continue;
}
seen.add(id);
const s = await this.models['study'].getById(id, {}, false);
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadStudiesForBulkCloseByIds issues one getById query per id and awaits them sequentially, which becomes an N+1 bottleneck for large bulk-closes. Consider fetching all studies in one query (e.g. this.models.study.getAllByKeyValues('id', ids) / findAll({ where: { id: { [Op.in]: ids } } })) and then filtering by projectId/template in-memory.

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +160
const resolvedStudyIds = StudySocket.resolveBulkCloseStudyIds(data);
const idListMode = data.bulkCloseUseIdList === true;

let studies;
if (idListMode) {
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several new branching behaviors here (strict bulkCloseUseIdList mode, studyIdsJson parsing, and id-list vs legacy filter selection), but there are no socket tests covering studyCloseBulk in backend/tests/socket.study.test.js. Adding integration tests for the success path (closes exactly the provided ids) and error path (strict mode with missing/invalid id list) would help prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to 18
// Use override: true so values from .env win over empty/partial Windows user/shell variables
// (dotenv’s default is to never overwrite existing process.env keys).
require('dotenv').config({ path: rootEnv, override: true });
if (process.env.ENV) {
const envFile = path.join(repoRoot, `.env.${process.env.ENV}`);
if (fs.existsSync(envFile)) {
require('dotenv').config({ path: envFile, override: true });
}
}

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using dotenv.config(..., override: true) in .sequelizerc can unintentionally override explicitly-provided environment variables when running migrations (e.g. CI/prod setting DB credentials via the shell), potentially pointing migrations at the wrong database or using unexpected secrets. Safer options are to avoid override: true, only load .env when required (e.g. local/dev), or only override variables when the existing process.env value is empty.

Suggested change
// Use override: true so values from .env win over empty/partial Windows user/shell variables
// (dotenv’s default is to never overwrite existing process.env keys).
require('dotenv').config({ path: rootEnv, override: true });
if (process.env.ENV) {
const envFile = path.join(repoRoot, `.env.${process.env.ENV}`);
if (fs.existsSync(envFile)) {
require('dotenv').config({ path: envFile, override: true });
}
}
// Load .env values without overriding any existing process.env keys (e.g. CI/prod settings).
require('dotenv').config({ path: rootEnv });
if (process.env.ENV) {
const envFile = path.join(repoRoot, `.env.${process.env.ENV}`);
if (fs.existsSync(envFile)) {
require('dotenv').config({ path: envFile });
}
}

Copilot uses AI. Check for mistakes.
@junaidferoz junaidferoz changed the title [FEATURE] it would be good to be able to select types of studies, to be able to not close all but still as a bulk request Feature #134 closing studies based on selection Mar 30, 2026
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this change; it doesn't seem to be part of this feature request.

@@ -1,3 +1,5 @@
# accidental SQL/export artifact at repo root (long filename)
/select CHANGELOG.md CONTRIBUTORS.md Dockerfile LICENSE Makefile NOTICE README.md backend docker-compose.yml docker-dev.yml docs files frontend logs msmtprc utils from Sessions
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand that change. There are files listed that should be in the repo?

},
methods: {
open() {
<<<<<<< feat-134-close_all_studies_options
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is something wrong, I think this was a failed merge request

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is difficult to read, as there are multiple merges failed, please check again

!study.closed
);
},
workflowOptions() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a bit more readable:

workflowOptions() {
  return [...new Set(this.openStudiesInProject.map(s => s.workflowId))]
    .filter(id => id != null)
    .map(id => {
      const wf = this.$store.getters["table/workflow/get"](id);
      return {
        value: id,
        name: wf?.name ? `${wf.name} (id ${id})` : `Workflow ${id}`,
      };
    })
    .sort((a, b) => a.name.localeCompare(b.name));
}

with seen it is maybe a bit more memory efficient, but here not needed, readable is more important

Do the same with the other function

Comment on lines +193 to +196
const matches = this.$store.getters["table/user_role_matching/getFiltered"](
(m) => !m.deleted && Number(m.userId) === uid && m.userRoleId === role.id
);
return matches.length > 0;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should do that, work with computed here

class="btn-secondary btn-sm"
title="Close All Studies"
text="Close All Studies"
title="Bulk close open studies (optional workflow and user filters)"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for (optional workflow and user filters), who knows what else we will add

}

/**
<<<<<<< feat-134-close_all_studies_options
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here you also have some conflicts

class StudySocket extends Socket {

/**
* Normalizes study id list from bulk-close payload (array may be lost or re-shaped in transit).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean here? Why is that needed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] it would be good to be able to select types of studies, to be able to not close all but still as a bulk request

3 participants