summary: 'Add an optional comma-separated `typeName` query parameter to GET /api/v2/projects/{projectId}/issues. When supplied, the issue list is filtered server-side to issues whose IssueType.TypeName matches one of the provided values (OR semantics). Omitting the parameter returns the full, unchanged list. This is a read-only, additive, backward-compatible change to the existing issues list endpoint. ' target_service: api2 postgres_changes: - kind: alter_procedure_or_function object: fn_GetIssueList schema: xyz file: Database/xyz/Functions/fn_GetIssueList.sql notes: "Amend the existing issue-list function that serves\nGET /issues to accept\ \ a NEW optional parameter\n`_typeNames TEXT[] DEFAULT NULL` (the controller passes\ \ the parsed,\ncomma-split list; NULL/empty array means \"no filter\").\nThe function\ \ already resolves _projectShardId via\nxyz.\"fn_GetProjectShardId\"(_projectId)\ \ and filters by ProjectShardId.\nAdd a JOIN to xyz.\"IssueType\" it ON it.\"\ ProjectShardId\" = i.\"ProjectShardId\"\nAND it.\"IssueTypeId\" = i.\"IssueTypeId\"\ , and add the predicate:\n(_typeNames IS NULL OR cardinality(_typeNames) = 0 OR\ \ it.\"TypeName\" = ANY(_typeNames)).\nMatch by TypeName string (NOT IssueTypeId)\ \ because IssueTypeId is\nproject-scoped via composite PK (ProjectShardId, IssueTypeId).\n\ Reference the filter predicate precedent in fn_GetLegacyIssue.sql:115.\nThis is\ \ a read-only function change: it must NOT alter the existing\nRETURNS TABLE column\ \ set, ordering, pagination/lastFetchedIndexId\nsemantics, or default (no-arg\ \ / NULL-arg) behavior \u2014 when _typeNames is\nNULL the result must be byte-for-byte\ \ identical to today.\nFile is edited in place (runOnChange:true), body starts\ \ with\nDROP FUNCTION IF EXISTS then CREATE OR REPLACE, endDelimiter:/, last line\ \ /.\nNOTE: This adds an optional parameter to a read function; it does NOT\n\ add/remove/alter any COLUMN on the Issue or IssueType tables, so no\ntable DDL\ \ and no general read/write proc column-set amendment is\nrequired. The specialist\ \ must confirm the actual function name serving\nGET /issues (likely fn_GetIssueList\ \ / fn_GetIssues) and amend that one\nrather than creating a new narrow function.\n" open_questions_resolution: "Decisions for the specialist (pending human confirmation\ \ at Gate 2):\n- Case sensitivity: implement case-INSENSITIVE matching\n (LOWER(it.\"\ TypeName\") = ANY(SELECT LOWER(x) FROM unnest(_typeNames) x))\n to be forgiving\ \ for dashboard callers; flagged in risks.\n- Unknown typeName value: return the\ \ (possibly empty) filtered list, NOT\n a 400 \u2014 additive OR semantics; an\ \ unmatched value simply contributes\n no rows. Flagged in risks.\n- No hard\ \ cap on number of typeName values in the DB layer; API2\n validator may bound\ \ it (see api2_changes). Flagged in risks.\n" citus_changes: none index_changes: none reporting_changes: none seed_data_changes: none mongo_changes: none api2_changes: - method: GET path: /api/v2/projects/{projectId}/issues file: src/api/v2/projects/issues/issues.routes.ts controller_file: src/api/v2/projects/issues/issues.controller.ts validator_file: src/api/v2/projects/issues/issues.validator.ts service_module: src/services/issues.service.ts db_calls: - fn_GetIssueList required_permissions: - ISSUE_VIEW swagger_tag: Issues notes: "Add a NEW optional query parameter `typeName` to the existing endpoint.\n\ - Parse it as a comma-separated string into a string[] in the controller\n (e.g.\ \ typeName=Quality,Safety -> [\"Quality\",\"Safety\"]). Trim each\n value; drop\ \ empty tokens.\n- When the param is absent OR resolves to an empty array, pass\ \ NULL to\n the service so fn_GetIssueList returns the full unfiltered list \u2014\ \ the\n response shape, contents, and pagination/lastFetchedIndexId behavior\n\ \ MUST be identical to today (no regression for web, XYZMobile, AtomOS).\n- The\ \ service passes the parsed array to the SINGLE existing\n fn_GetIssueList call\ \ (extra trailing parameter). Do NOT add a second\n DB round-trip and do NOT\ \ post-filter in application code \u2014 filtering\n is applied server-side inside\ \ the function.\n- validator: typeName is optional string; if present, validate\ \ it is a\n non-empty comma-separated list. Optionally cap the number of values\n\ \ (see risks/open question) \u2014 keep validation additive and non-breaking.\n\ - Update the swagger annotation for this route to document the new\n optional\ \ `typeName` query parameter (description: comma-separated\n issue type names,\ \ additive OR semantics; omit for full list).\n- Update the egress DTO ONLY if\ \ needed; response shape is unchanged, so\n no DTO field changes are expected.\n\ - Tests/mocks/fixtures: update existing issues-list unit and e2e tests to\n cover\ \ (a) no typeName -> unchanged behavior, (b) single typeName,\n (c) multiple\ \ comma-separated typeNames, (d) typeName matching nothing.\n" api1_changes: none inter_service_calls: none new_permissions: none java_frozen_resources: - resource: issues confirmation: 'The `issues` resource is dual-API (present in both API2 and API1 hc-project). All changes for this feature land on API2 + Postgres only. No API1 hc-project code (e.g. IssueResource) is modified. The hc-project Java issue surface remains frozen/read-only. ' risks: - 'Open question (case sensitivity): plan assumes case-INSENSITIVE TypeName matching for dashboard friendliness. If product requires case-sensitive matching, the specialist must switch the predicate to a direct `it."TypeName" = ANY(_typeNames)`. Confirm at Gate 2. ' - 'Open question (unknown typeName): plan assumes an unmatched typeName value simply yields no rows (filtered list may be empty), NOT a 400 and NOT a silent fallback to the full list. Confirm this is the desired UX. ' - 'Open question (max values): plan does not hard-cap the number of typeName values at the DB layer; an optional bound can be enforced in the API2 validator. Confirm whether a cap is required. ' - 'Backward-compatibility is load-bearing: three clients (web, XYZMobile, AtomOS) consume GET /issues. The NULL/absent path through fn_GetIssueList must remain byte-identical. Existing e2e tests for the unfiltered list must stay green. ' - 'The exact name of the function currently backing GET /issues must be confirmed by the API2/Postgres specialists (candidate: fn_GetIssueList). Amend that general list function rather than introducing a parallel narrow function, to keep the read path single-round-trip and consistent. ' out_of_scope: - Filtering by issueTypeId (project-scoped composite PK makes this unsuitable). - Any change to response shape, egress DTO fields, or pagination behavior. - Client-side changes beyond the dashboard switching to typeName=Quality. - Phase 2+ payload-size optimizations beyond this filter parameter. - Any write-path or schema/column changes to Issue or IssueType tables. testing_plan: "API2:\n- Update existing issues-list unit tests (controller/service)\ \ to verify the\n typeName query param is parsed (comma-split, trimmed) and passed\ \ to\n fn_GetIssueList as an array; verify NULL is passed when absent/empty.\n\ - Update/extend e2e tests for GET /issues:\n * no typeName -> identical response\ \ to baseline (regression guard).\n * typeName=Quality -> only Quality-type issues\ \ returned.\n * typeName=Quality,Safety -> issues of either type (OR semantics).\n\ \ * typeName=NonExistent -> empty records, valid PaginationEnvelope.\n- Ensure\ \ `npm test` stays green (no regressions for unfiltered path).\nPostgres:\n- Extend\ \ IntegrationTest/main.py with a scenario calling fn_GetIssueList with\n and without\ \ _typeNames, asserting that the NULL-arg result matches the\n pre-change result\ \ and that the filtered result restricts by TypeName.\n- Verify the function still\ \ returns the same column set and ordering." _meta: model: claude-opus-4-8 atom_ids: - endpoint.api2.get__api_v2_projects__projectId__issues__issueId__history - endpoint.api2.get__api_v2_projects__projectId__issues_types - endpoint.api2.get__api_v2_projects__projectId__issues_parameters - endpoint.api2.put__api_v2_projects__projectId__issues_activity-categories__issueId__link - rule.architect_rules - endpoint.api2.get__api_v2_projects__projectId__issues - endpoint.api2.get__api_v2_projects__projectId__issues__issueId_ - endpoint.api2.patch__api_v2_projects__projectId__issues__issueId_ - endpoint.api2.get_api_v2_projects__projectId__issues__issueId__comments - endpoint.api2.patch__api_v2_projects__projectId__issues_activity-categories__issueId__unlink - endpoint.api2.put__api_v2_projects__projectId__issues__issueId__file-references_link - endpoint.api2.put__api_v2_projects__projectId__issues__issueId__models_link - pg.xyz.Issue - pg.xyz.IssueActivityCategoryMapping - pg.xyz.IssueComment - pg.xyz.IssueFileReferenceMapping - pg.xyz.IssueModelMapping - pg.staging.MigratedCdeIssueType - pg.xyz.IssueType - pg.staging.MigratedIssueType - pg.xyz.IssueCustomAttributeIssueTypeMapping - dto.api1-hc-project.CdeIssueType spec: title: 'GET /issues: additive typeName filter query param' project_kind: modify user_facing_behavior: "GET /issues accepts an optional comma-separated `typeName`\ \ query parameter. When supplied, the response contains only issues whose type\ \ name matches one of the provided values (additive / OR semantics). Omitting\ \ the parameter returns the full, unfiltered issue list \u2014 identical to\ \ current behavior. The dashboard will pass `typeName=Quality` to avoid fetching\ \ and immediately discarding irrelevant issue types, reducing the ~11.7 MB payload." data_touched: - Issue - IssueType api_surface: read non_functional_requirements: - Filtering must be applied server-side (not post-fetch in application code). - Matching is by typeName string, not typeId, because typeId is project-scoped via composite PK (ProjectShardId, IssueTypeId). - "Backward compatibility: omitting typeName must produce identical response shape\ \ and contents as today \u2014 no regressions for web, XYZMobile, or AtomOS." - Precedent query logic in fn_GetLegacyIssue.sql:115 should be referenced when implementing the filter predicate. - Multiple type names are comma-separated (e.g. typeName=Quality,Safety). out_of_scope: - Filtering by typeId. - Any changes to response shape or pagination behavior. - Client-side changes beyond the dashboard switching to typeName=Quality. - Phase 2+ payload optimizations beyond this filter param. open_questions: - Should typeName matching be case-sensitive or case-insensitive? - "What should the API return when a supplied typeName value matches no known\ \ type \u2014 empty list, full list, or a 400 error?" - Is there a maximum number of typeName values that should be accepted in a single request?