Skip to content

Feature: Add data source folder management#15663

Open
akshaysasidrn wants to merge 27 commits intomainfrom
feature/ds-folders-main
Open

Feature: Add data source folder management#15663
akshaysasidrn wants to merge 27 commits intomainfrom
feature/ds-folders-main

Conversation

@akshaysasidrn
Copy link
Copy Markdown
Collaborator

@akshaysasidrn akshaysasidrn commented Mar 23, 2026

📝 What this does

Adds full backend for data source folders — CRUD, move, listing, granular permissions, and runtime query-run enforcement. Organizes data sources into folders with fine-grained access control at the folder level, gated behind the folderDataSourceCreate/folderDataSourceDelete license permissions.

Relates to: tj-ee #4814, tj-ee #4815

Sub-issues:

  • #4852 Create, rename, and delete data source folders
  • #4853 Move data sources between folders
  • #4854 List and search folders with data source counts
  • #4855 Git sync and branching integration — SKIPPED
  • #4856 Granular folder-level permissions
  • #4857 Restrict query execution per folder

🏗️ Architecture

Entity Model

erDiagram
    Folder {
        uuid id PK
        string name
        string type "front_end | data_source"
        uuid organizationId FK
        uuid createdBy FK
    }
    FolderDataSource {
        uuid id PK
        uuid folderId FK
        uuid dataSourceId FK
    }
    GranularPermissions {
        uuid id PK
        uuid groupId FK
        string type "folder_data_source"
    }
    FolderDataSourcesGroupPermissions {
        uuid id PK
        uuid granularPermissionId FK
        boolean canEditFolder
        boolean canConfigureDs
        boolean canUseDs
        boolean canRunQuery
    }
    GroupFolderDataSources {
        uuid id PK
        uuid folderDataSourcesGroupPermissionsId FK
        uuid folderId FK
    }

    Folder ||--o{ FolderDataSource : contains
    FolderDataSource }o--|| DataSource : references
    GranularPermissions ||--|| FolderDataSourcesGroupPermissions : has
    FolderDataSourcesGroupPermissions ||--o{ GroupFolderDataSources : targets
    GroupFolderDataSources }o--|| Folder : references
Loading

Permission Hierarchy

graph TD
    A[canEditFolder] -->|auto-sets| B[canConfigureDs]
    B -->|auto-sets| C[canUseDs]
    D[canRunQuery] -.->|independent| C
    style A fill:#4f46e5,color:#fff
    style B fill:#7c3aed,color:#fff
    style C fill:#9333ea,color:#fff
    style D fill:#dc2626,color:#fff
Loading
 ┌─────────────────────────────────────────────────────────────────────────────┐
 │ Granular Access Tab                                                         │
 │                                                                             │
 │  Name          Permission (radio)         Environment    Resource           │
 │  ───────────────────────────────────────────────────────────────────────────│
 │                                                                             │
 │  📱 Apps       ● Edit       ○ View        Dev/Stg/Rel    All apps           │
 │                canEdit       canView                                        │
 │                                                                             │
 │  🔌 Data       ● Configure  ○ Build with  All envs       All data sources   │
 │  sources       canConfigure  canUse                                         │
 │                                                                             │
 │  ⚙️ Workflows  ● Build      ○ Execute     All envs       All workflows      │
 │                canBuild      canExecute                                     │
 │                                                                             │
 │  📁 Folders    ● Edit folder ○ Edit apps  ○ View apps  All envs  All fldrs  │
 │  (app)         canEditFolder  canEditApps  canViewApps                      │
 │                                                                             │
 │  📁 Data       ● Edit folder ○ Configure  ○ Build with All envs  All fldrs  │
 │  source        canEditFolder  canConfigDs  canUseDs                         │
 │  folders       ☐ Restrict query run  ← independent checkbox                 │
 │  (NEW)           canRunQuery=false                                          │
 │                                                                             │
 └─────────────────────────────────────────────────────────────────────────────┘

When a data source (e.g. pgsql1) has BOTH a folder-level permission AND a singular DS permission:

 ┌─────┬───────────────────────────────┬───────────────────────────┬────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
 │  #  │ Folder perm (contains pgsql1) │ Singular DS perm (pgsql1) │ Effective access to pgsql1 │                                            Notes                                             │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 1   │ Edit folder                   │ —                         │ Configure + Build + Move   │ —                                                                                            │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 2   │ Configure DS                  │ —                         │ Configure + Build          │ —                                                                                            │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 3   │ Build with DS                 │ —                         │ Build only                 │ —                                                                                            │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 4   │ Edit folder                   │ Configure                 │ Configure + Build + Move   │ Folder gives all; singular is redundant                                                      │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 5   │ Configure DS                  │ Configure                 │ Configure + Build          │ —                                                                                            │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 6   │ Build with DS                 │ Configure                 │ Configure + Build          │ Singular DS elevates pgsql1 beyond folder's Build-only; other DS in folder remain Build-only │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 7   │ Edit folder                   │ Build with                │ Configure + Build + Move   │ Folder gives all; singular is redundant                                                      │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 8   │ Configure DS                  │ Build with                │ Configure + Build          │ Folder gives both; singular is redundant                                                     │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 9   │ Build with DS                 │ Build with                │ Build only                 │ —                                                                                            │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 10  │ Restrict query run            │ —                         │ Cannot run query           │ Blocks ALL DS in folder                                                                      │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 11  │ —                             │ Restrict query run        │ Cannot run query           │ Blocks pgsql1 only                                                                           │
 ├─────┼───────────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
 │ 12  │ Restrict query run            │ Restrict query run        │ Cannot run query           │ Redundant — blocked by both                                                                  │
 └─────┴───────────────────────────────┴───────────────────────────┴────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘

Key rules:

  • Permissions are additive/union — the user gets the HIGHEST access from any source (cases 6, 7, 8)
  • Exception: canRunQuery=false is deny-biased — if ANY group the user belongs to blocks query run for a folder containing the DS, queries are blocked

Query Run Enforcement Flow

sequenceDiagram
    participant Client
    participant DataQueriesService
    participant DB

    Client->>DataQueriesService: runAndGetResult(user, dataQuery)
    DataQueriesService->>DB: find FolderDataSource where dataSourceId = X
    alt DS not in any folder
        DataQueriesService->>Client: execute query normally
    else DS in folder(s)
        DataQueriesService->>DB: find user's groupIds via GroupUsers
        DataQueriesService->>DB: join GroupFolderDataSources → FolderDataSourcesGroupPermissions WHERE canRunQuery = false
        alt match found
            DataQueriesService->>Client: 403 ForbiddenException
        else no match
            DataQueriesService->>Client: execute query normally
        end
    end
Loading

Folder Visibility

Non-admin users see only folders they have access to:

  1. Granular access — folders granted via FolderDataSourcesGroupPermissions (specific or isAll)
  2. Owner bypass — folders the user created, if they hold the folderDataSourceCreate master permission
  3. Admin/superAdmin — see all folders

🔌 API Reference

Folder CRUD & Data Source Management

Method Route Permission Request Response
GET /api/data-source-folders GET_FOLDER_DATA_SOURCES Query: search (optional), include_data_sources (optional, true) [{ id, name, type, organizationId, count }] — with include_data_sources=true: [{ id, name, count, data_sources: [{ id, name, kind, ... }] }] (includes ungrouped bucket with id: null)
POST /api/data-source-folders CREATE_FOLDER_DATA_SOURCE { name } (1-50 chars, [a-zA-Z0-9_ -]) { id, name, ... }
PUT /api/data-source-folders/:id UPDATE_FOLDER_DATA_SOURCE { name } Update result
DELETE /api/data-source-folders/:id DELETE_FOLDER_DATA_SOURCE Delete result
GET /api/data-source-folders/:id/data-sources GET_DATA_SOURCES_IN_FOLDER Query: page (default 1), per_page (default 25) { data_sources: [DataSource], meta: { total_count, total_pages, current_page, per_page } }
POST /api/data-source-folders/:id/data-sources ADD_DATA_SOURCE_TO_FOLDER { dataSourceId: UUID } Created join
DELETE /api/data-source-folders/:id/data-sources/:dsId REMOVE_DATA_SOURCE_FROM_FOLDER Delete result
PUT /api/data-source-folders/:id/data-sources BULK_MOVE_DATA_SOURCES { dataSourceIds: UUID[] } Created joins

Granular Folder Permissions (EE)

Method Route Permission Request Response
POST /api/v2/group-permissions/:groupId/granular-permissions/data-source-folder CREATE_GRANULAR_FOLDER_DATA_SOURCE_PERMISSIONS { name, isAll, type, createResourcePermissionObject } { id, name, type, isAll, canEditFolder, canConfigureDs, canUseDs, canRunQuery }
PUT /api/v2/group-permissions/:groupId/granular-permissions/data-source-folder/:permId UPDATE_GRANULAR_FOLDER_DATA_SOURCE_PERMISSIONS { actions, resourcesToAdd, resourcesToDelete } Update result
DELETE /api/v2/group-permissions/:groupId/granular-permissions/data-source-folder/:permId DELETE_GRANULAR_FOLDER_DATA_SOURCE_PERMISSIONS void
GET /api/v2/group-permissions/granular-permissions/addable-folders GET_ADDABLE_FOLDER_DATA_SOURCES [{ id, name }]

🔀 Changes

  • Added FolderDataSource entity, junction table, and 4 migrations for DS folder schema
  • Implemented CRUD, move (single + bulk), listing, and search endpoints with auth guards
  • Added granular permission hierarchy with FolderDataSourcesGroupPermissions entity and enforcement
  • Split folderDataSourceCRUD into folderDataSourceCreate/folderDataSourceDelete (entity, migration, types, DTOs, constants, ability services)
  • Renamed restrictQueryRun to canRunQuery for consistency with the can* naming convention
  • Renamed all ds_folder/DsFolder (now folder_data_source/FolderDataSource) abbreviations to fully spelled folder_data_source/FolderDataSource
  • Implemented permission-based folder listing in EE — non-admin users only see folders they have granular access to or created
  • Wired canRunQuery enforcement at query execution time in EE data-queries service
  • Fixed GroupExistenceGuard NestJS DI resolution for EE edition
  • Restored app-folder permission types lost during lts→main merge
  • Merged origin/main (PR Feat: Folder permission system #15028 app-folder permission system) into feature branch

🧪 How to test

  • Run migrations and start server
  • Create a data source folder via POST /api/data-source-folders
  • Add a global-scope data source to the folder
  • Verify listing with DS counts via GET /api/data-source-folders
  • Bulk move data sources between folders
  • Create granular permissions on the folder for a custom group
  • Verify hierarchy: setting canEditFolder=true auto-sets canConfigureDs and canUseDs
  • Set canRunQuery=false on a group and verify queries on that DS return 403
  • Verify non-admin user only sees folders they have granular access to
  • Verify folder creator sees their own folder (owner bypass)
  • Run e2e suite: npx jest --config test/jest-e2e.json --testPathPatterns=folder-data-sources (50/50 pass)

vjaris42 and others added 11 commits March 19, 2026 22:00
* feat: Folder permission system

* fix(group-permissions): resolve custom group validation, folder edit check, and UI inconsistencie

* edit folder container && no folder in custom resource

* fix the ui for custom in empty state

* fix: coercion logic for folder permissions

* feat: enhance folder permissions handling in app components

* feat: add folder granular permissions handling in user apps permissions

* feat: implement granular folder permissions in ability guard and service

* feat: improve error handling for folder permissions with specific messages

* feat: enhance EnvironmentSelect component to handle disabled state and improve display logic

* chore: bump ee submodules

* feat: Update permission prop to isEditable in BaseManageGranularAccess component

* chore: bump ee server submodule

* fix: refine folder visibility logic based on user permissions

* feat: enhance MultiValue rendering and styling for "All environments" option

* feat: implement folder ownership checks and enhance app permissions handling

* feat: enhance folder permissions handling for app ownership and actions

* chore: clarify folder creation and deletion permissions in workspace context

* fix: update folder permission labels

* fixed folder permission cases

* fixed css class issue

* minor fix

* feat: streamline folder permissions handling by removing redundant checks and simplifying access logic

* refactor: made error message consistent

* fix: add missing permission message for folder deletion action

* refactor: consolidate forbidden messages for folder actions and maintain consistency

* feat: streamline permission handling and improve app visibility logic

* fix: remove default access denial message in AbilityGuard

* fixed all user page functionality falky case

* Fixed profile flaky case

* fixed granular access flaky case

* chore: revert package-lock.json

* chore: revert frontend/package-lock.json to main

* refactor: revert folder operations

* fix: remove unused AppEnvironmentsModule and AppsUtilService exports

---------

Co-authored-by: gsmithun4 <gsmithun4@gmail.com>
Co-authored-by: Pratush <pratush@Pratushs-MBP.lan>
Co-authored-by: Shantanu Mane <maneshantanu.20@gmail.com>
Co-authored-by: Yukti Goyal <yuktigoyal02@gmail.com>
Co-authored-by: gsmithun4 <3417097+gsmithun4@users.noreply.github.com>
…se 1)

Cherry-picked from feature/ds-folders (c7d4184) onto main.
Resolved EE submodule conflicts: kept main's folderCreate/folderDelete
alongside new dataSourceFolderCRUD.
Add endpoints for moving data sources between folders:
- POST /:id/data-sources — add DS to folder (auto-removes from previous)
- DELETE /:id/data-sources/:dsId — remove DS from folder
- PUT /:id/data-sources — atomic bulk move with transaction
- Validates only global-scope DS can be added (rejects app-scoped with 400)
- 12 new e2e tests covering add, move, remove, bulk, permissions

Closes ToolJet/tj-ee#4853

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add GET endpoints for folder-aware data source browsing:
- GET /data-source-folders — list folders alphabetically with DS count, optional ?search= filter
- GET /data-source-folders/:id/data-sources — list data sources within a folder
- Util service uses QueryBuilder with LEFT JOIN + GROUP BY for counts
- 7 new e2e tests covering listing, search, empty states

Closes ToolJet/tj-ee#4854

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Phase 5)

New entities (DsFoldersGroupPermissions, GroupDsFolders), migration,
controller routes, and service logic for folder-level granular permissions.
Hierarchy: edit folder > configure DS > build with DS. End-user groups
restricted to restrictQueryRun only. Admin/Builder get full access defaults.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restores FOLDER enum, entities (FoldersGroupPermissions, GroupFolders),
type definitions, FEATURE_KEY entries, and relation mappings that were
dropped when PR #15028 was merged to main. Also fixes EE constants to
use folderCRUD (matching actual DB schema) instead of folderCreate/
folderDelete.
- Add dataSourceFolderCRUD ability constants and type definitions
- Add Phase 5 granular permissions migration (1774255111000)
- Wire DS folder permission DTOs and type exports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add .send({ dataSourceIds: [] }) to PUT auth test to avoid 400
  from ValidationPipe running before JwtAuthGuard
- Wire folder type awareness into folders service for DS folders
- Update app service for DS folder support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Point EE submodule to DS folder branch with Phase 1-6 commits
- Update plugin dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Test query blocked (403) when restrictQueryRun=true on DS folder
- Test query allowed when restrictQueryRun=false
- Test query allowed when DS is not in any folder

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@akshaysasidrn akshaysasidrn changed the title Feature: Add data source folder management (Phases 1-6) Feature: Add data source folder management Mar 23, 2026
akshaysasidrn and others added 12 commits March 24, 2026 00:39
Resolves ~19 merge conflicts from main's PR #15028 (app-folder
permission system). Key changes:

- Replace folderCRUD with folderCreate/folderDelete (main's split)
- Keep dataSourceFolderCRUD for DS folder feature
- Accept main's folder entity (createdBy), ability refactor, folder
  permission guards, and CASL rules
- Add DS folder types/endpoints alongside main's folder types
- Delete redundant migration 1774255111000 (superseded by main's
  1766500000000-AddFolderPermissionSystem)
- Fix EE external-apis and ability services for new column names
- Update all test helpers to use new column names

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove duplicate [ResourceType.FOLDER] entries, duplicate FEATURE_KEY
entries, and duplicate interface declarations from the auto-merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…onService

Fixes pre-existing rename from main merge in workflows test helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CE base method now returns FoldersGroupPermissions to match EE override,
fixing class hierarchy type error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… visibility

- Replace monolithic dataSourceFolderCRUD with granular
  dataSourceFolderCreate/dataSourceFolderDelete across types, entity,
  migration, constants, DTOs, ability services, and tests
- Implement permission-based folder listing in EE: non-admin users
  only see folders they have granular access to
- Add owner bypass: folder creators see their own folders when they
  hold the dataSourceFolderCreate master permission
- Convert 3 todo tests into passing implementations (50/50 green)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All other permission columns use the can* prefix. Rename restrictQueryRun
(default false, true=blocked) to canRunQuery (default true, false=blocked)
to match the convention. Same enforcement semantics, consistent naming.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ard DI

- Rename all ds_folder/DsFolder/DS_FOLDER abbreviations to fully spelled
  data_source_folder/DataSourceFolder/DATA_SOURCE_FOLDER per PRD convention
- Rename tables: folder_data_sources → data_source_folders,
  ds_folders_group_permissions → data_source_folders_group_permissions,
  group_ds_folders → group_data_source_folders
- Rename feature keys: CREATE_DS_FOLDER → CREATE_DATA_SOURCE_FOLDER etc.
- Fix GroupExistenceGuard NestJS DI: register guard as provider and alias
  CE GroupPermissionsUtilService to dynamically loaded EE instance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Support fetching folders with nested data source arrays in a single API
call, eliminating N+1 requests from the frontend. Includes search
filtering by folder name and DS name, ungrouped DS bucket, and EE
permission filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…→ folder_data_source

- Add error message verification to all error-path tests (400, 401, 403, 409)
- Convert individual key assertions to structural toMatchObject checks
- Rename permission tests to intent-based names (e.g., 'canEditFolder=true auto-elevates...')
- Remove defensive if(404/405) early-exits from granular permission CRUD tests
- Rename data_source_folder → folder_data_source across entities, constants, and types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ments

- Standardize on TIMESTAMPTZ NOT NULL DEFAULT now() across all DS folder migrations
- Rename migration file to drop Ds abbreviation
- Remove restating-the-code comments from migrations and services
- Remove unused DTO aliases
- Update submodule pointer for server/ee
Revert plugins/package.json (mariadb addition), lock files,
and useAppData.js changes that were merge noise.
# Conflicts:
#	.version
#	frontend/.version
#	server/.version
#	server/ee
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants