@penclipai/server 2026.704.0 → 2026.714.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/process/execute.d.ts.map +1 -1
- package/dist/adapters/process/execute.js +1 -0
- package/dist/adapters/process/execute.js.map +1 -1
- package/dist/adapters/registry.d.ts.map +1 -1
- package/dist/adapters/registry.js +79 -51
- package/dist/adapters/registry.js.map +1 -1
- package/dist/adapters/utils.d.ts +5 -0
- package/dist/adapters/utils.d.ts.map +1 -1
- package/dist/adapters/utils.js.map +1 -1
- package/dist/agent-auth-jwt.d.ts +5 -1
- package/dist/agent-auth-jwt.d.ts.map +1 -1
- package/dist/agent-auth-jwt.js +72 -20
- package/dist/agent-auth-jwt.js.map +1 -1
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +10 -0
- package/dist/app.js.map +1 -1
- package/dist/built-ins/agents/reflection-coach/AGENTS.md +47 -0
- package/dist/built-ins/agents/reflection-coach/routines/recent-agent-reflection.md +77 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +208 -116
- package/dist/index.js.map +1 -1
- package/dist/middleware/api-compression.d.ts +8 -0
- package/dist/middleware/api-compression.d.ts.map +1 -0
- package/dist/middleware/api-compression.js +207 -0
- package/dist/middleware/api-compression.js.map +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +130 -3
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +27 -0
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/onboarding-assets/default/AGENTS.md +1 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +11 -0
- package/dist/redaction.js.map +1 -1
- package/dist/routes/access.d.ts.map +1 -1
- package/dist/routes/access.js +4 -3
- package/dist/routes/access.js.map +1 -1
- package/dist/routes/adapters.d.ts.map +1 -1
- package/dist/routes/adapters.js +2 -0
- package/dist/routes/adapters.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +222 -87
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/attention.d.ts +3 -0
- package/dist/routes/attention.d.ts.map +1 -0
- package/dist/routes/attention.js +24 -0
- package/dist/routes/attention.js.map +1 -0
- package/dist/routes/authz.d.ts.map +1 -1
- package/dist/routes/authz.js +29 -1
- package/dist/routes/authz.js.map +1 -1
- package/dist/routes/board-chat.d.ts.map +1 -1
- package/dist/routes/board-chat.js +4 -1
- package/dist/routes/board-chat.js.map +1 -1
- package/dist/routes/built-in-agents.d.ts +3 -0
- package/dist/routes/built-in-agents.d.ts.map +1 -0
- package/dist/routes/built-in-agents.js +267 -0
- package/dist/routes/built-in-agents.js.map +1 -0
- package/dist/routes/cases.d.ts +46 -0
- package/dist/routes/cases.d.ts.map +1 -0
- package/dist/routes/cases.js +1395 -0
- package/dist/routes/cases.js.map +1 -0
- package/dist/routes/companies.d.ts.map +1 -1
- package/dist/routes/companies.js +4 -1
- package/dist/routes/companies.js.map +1 -1
- package/dist/routes/company-skills.d.ts.map +1 -1
- package/dist/routes/company-skills.js +453 -33
- package/dist/routes/company-skills.js.map +1 -1
- package/dist/routes/environments.d.ts.map +1 -1
- package/dist/routes/environments.js +71 -8
- package/dist/routes/environments.js.map +1 -1
- package/dist/routes/execution-workspaces.d.ts.map +1 -1
- package/dist/routes/execution-workspaces.js +89 -3
- package/dist/routes/execution-workspaces.js.map +1 -1
- package/dist/routes/health.d.ts +2 -0
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +30 -0
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/inbox-dismissals.d.ts.map +1 -1
- package/dist/routes/inbox-dismissals.js +80 -19
- package/dist/routes/inbox-dismissals.js.map +1 -1
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/issues.d.ts +25 -0
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +1455 -46
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/openapi.d.ts.map +1 -1
- package/dist/routes/openapi.js +448 -11
- package/dist/routes/openapi.js.map +1 -1
- package/dist/routes/pipelines.js +2 -2
- package/dist/routes/pipelines.js.map +1 -1
- package/dist/routes/resource-memberships.d.ts.map +1 -1
- package/dist/routes/resource-memberships.js +13 -5
- package/dist/routes/resource-memberships.js.map +1 -1
- package/dist/routes/secrets.d.ts.map +1 -1
- package/dist/routes/secrets.js +261 -1
- package/dist/routes/secrets.js.map +1 -1
- package/dist/routes/sidebar-badges.d.ts.map +1 -1
- package/dist/routes/sidebar-badges.js +18 -2
- package/dist/routes/sidebar-badges.js.map +1 -1
- package/dist/routes/user-profiles.d.ts.map +1 -1
- package/dist/routes/user-profiles.js +5 -4
- package/dist/routes/user-profiles.js.map +1 -1
- package/dist/server-info.d.ts +2 -0
- package/dist/server-info.d.ts.map +1 -1
- package/dist/server-info.js +20 -5
- package/dist/server-info.js.map +1 -1
- package/dist/services/access.d.ts +4 -4
- package/dist/services/access.d.ts.map +1 -1
- package/dist/services/activity.d.ts +3 -3
- package/dist/services/activity.d.ts.map +1 -1
- package/dist/services/activity.js +5 -4
- package/dist/services/activity.js.map +1 -1
- package/dist/services/adapter-registry-bootstrap.reconcile.test.js +1 -0
- package/dist/services/adapter-registry-bootstrap.reconcile.test.js.map +1 -1
- package/dist/services/agent-secret-bindings.d.ts +15 -0
- package/dist/services/agent-secret-bindings.d.ts.map +1 -1
- package/dist/services/agent-secret-bindings.js +43 -0
- package/dist/services/agent-secret-bindings.js.map +1 -1
- package/dist/services/agents.d.ts +194 -133
- package/dist/services/agents.d.ts.map +1 -1
- package/dist/services/agents.js +107 -4
- package/dist/services/agents.js.map +1 -1
- package/dist/services/approvals.d.ts +10 -10
- package/dist/services/approvals.d.ts.map +1 -1
- package/dist/services/approvals.js +9 -1
- package/dist/services/approvals.js.map +1 -1
- package/dist/services/assets.d.ts +5 -5
- package/dist/services/attention.d.ts +11 -0
- package/dist/services/attention.d.ts.map +1 -0
- package/dist/services/attention.js +1024 -0
- package/dist/services/attention.js.map +1 -0
- package/dist/services/authorization.d.ts +15 -2
- package/dist/services/authorization.d.ts.map +1 -1
- package/dist/services/authorization.js +324 -8
- package/dist/services/authorization.js.map +1 -1
- package/dist/services/board-auth.d.ts +2 -2
- package/dist/services/budgets.d.ts.map +1 -1
- package/dist/services/budgets.js +8 -7
- package/dist/services/budgets.js.map +1 -1
- package/dist/services/built-in-agent-metadata.d.ts +9 -0
- package/dist/services/built-in-agent-metadata.d.ts.map +1 -0
- package/dist/services/built-in-agent-metadata.js +39 -0
- package/dist/services/built-in-agent-metadata.js.map +1 -0
- package/dist/services/built-in-agents.d.ts +394 -0
- package/dist/services/built-in-agents.d.ts.map +1 -0
- package/dist/services/built-in-agents.js +1426 -0
- package/dist/services/built-in-agents.js.map +1 -0
- package/dist/services/change-consent-gate.d.ts +18 -0
- package/dist/services/change-consent-gate.d.ts.map +1 -0
- package/dist/services/change-consent-gate.js +170 -0
- package/dist/services/change-consent-gate.js.map +1 -0
- package/dist/services/companies.d.ts +14 -8
- package/dist/services/companies.d.ts.map +1 -1
- package/dist/services/companies.js +6 -1
- package/dist/services/companies.js.map +1 -1
- package/dist/services/company-artifacts.d.ts +4 -1
- package/dist/services/company-artifacts.d.ts.map +1 -1
- package/dist/services/company-artifacts.js +9 -1
- package/dist/services/company-artifacts.js.map +1 -1
- package/dist/services/company-member-roles.d.ts.map +1 -1
- package/dist/services/company-member-roles.js +2 -0
- package/dist/services/company-member-roles.js.map +1 -1
- package/dist/services/company-portability.d.ts +1 -1
- package/dist/services/company-portability.d.ts.map +1 -1
- package/dist/services/company-portability.js +113 -8
- package/dist/services/company-portability.js.map +1 -1
- package/dist/services/company-search.d.ts.map +1 -1
- package/dist/services/company-search.js +843 -294
- package/dist/services/company-search.js.map +1 -1
- package/dist/services/company-skills.d.ts +64 -3
- package/dist/services/company-skills.d.ts.map +1 -1
- package/dist/services/company-skills.js +1205 -20
- package/dist/services/company-skills.js.map +1 -1
- package/dist/services/costs.d.ts +9 -8
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +9 -2
- package/dist/services/costs.js.map +1 -1
- package/dist/services/dashboard.d.ts +2 -0
- package/dist/services/dashboard.d.ts.map +1 -1
- package/dist/services/dashboard.js +60 -17
- package/dist/services/dashboard.js.map +1 -1
- package/dist/services/database-backup-health.d.ts +34 -0
- package/dist/services/database-backup-health.d.ts.map +1 -0
- package/dist/services/database-backup-health.js +104 -0
- package/dist/services/database-backup-health.js.map +1 -0
- package/dist/services/document-annotations.d.ts +169 -1
- package/dist/services/document-annotations.d.ts.map +1 -1
- package/dist/services/document-annotations.js +272 -1
- package/dist/services/document-annotations.js.map +1 -1
- package/dist/services/documents.d.ts +368 -0
- package/dist/services/documents.d.ts.map +1 -1
- package/dist/services/documents.js +2 -2
- package/dist/services/documents.js.map +1 -1
- package/dist/services/environment-config.d.ts.map +1 -1
- package/dist/services/environment-config.js +6 -0
- package/dist/services/environment-config.js.map +1 -1
- package/dist/services/environment-custom-image-runtime.d.ts +28 -0
- package/dist/services/environment-custom-image-runtime.d.ts.map +1 -1
- package/dist/services/environment-custom-image-runtime.js +106 -7
- package/dist/services/environment-custom-image-runtime.js.map +1 -1
- package/dist/services/environment-custom-images.d.ts +31 -1
- package/dist/services/environment-custom-images.d.ts.map +1 -1
- package/dist/services/environment-custom-images.js +110 -4
- package/dist/services/environment-custom-images.js.map +1 -1
- package/dist/services/environment-execution-target.d.ts.map +1 -1
- package/dist/services/environment-execution-target.js +1 -3
- package/dist/services/environment-execution-target.js.map +1 -1
- package/dist/services/environment-probe.d.ts +1 -0
- package/dist/services/environment-probe.d.ts.map +1 -1
- package/dist/services/environment-probe.js +99 -5
- package/dist/services/environment-probe.js.map +1 -1
- package/dist/services/environments.d.ts +3 -1
- package/dist/services/environments.d.ts.map +1 -1
- package/dist/services/environments.js +98 -3
- package/dist/services/environments.js.map +1 -1
- package/dist/services/execution-workspace-policy.d.ts +29 -3
- package/dist/services/execution-workspace-policy.d.ts.map +1 -1
- package/dist/services/execution-workspace-policy.js +44 -1
- package/dist/services/execution-workspace-policy.js.map +1 -1
- package/dist/services/execution-workspaces.d.ts +64 -1
- package/dist/services/execution-workspaces.d.ts.map +1 -1
- package/dist/services/execution-workspaces.js +634 -3
- package/dist/services/execution-workspaces.js.map +1 -1
- package/dist/services/external-objects.d.ts +26 -26
- package/dist/services/feedback.d.ts +2 -2
- package/dist/services/finance.d.ts +5 -5
- package/dist/services/goals.d.ts +8 -8
- package/dist/services/heartbeat-stop-metadata.d.ts +2 -2
- package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -1
- package/dist/services/heartbeat-stop-metadata.js +4 -0
- package/dist/services/heartbeat-stop-metadata.js.map +1 -1
- package/dist/services/heartbeat-stop-metadata.test.js +9 -0
- package/dist/services/heartbeat-stop-metadata.test.js.map +1 -1
- package/dist/services/heartbeat.d.ts +133 -28
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +1963 -239
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/inbox-dismissals.d.ts +26 -0
- package/dist/services/inbox-dismissals.d.ts.map +1 -1
- package/dist/services/inbox-dismissals.js +33 -18
- package/dist/services/inbox-dismissals.js.map +1 -1
- package/dist/services/index.d.ts +3 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-settings.d.ts +25 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +103 -5
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/issue-continuation-summary.js +1 -1
- package/dist/services/issue-continuation-summary.js.map +1 -1
- package/dist/services/issue-recovery-actions.d.ts +3 -0
- package/dist/services/issue-recovery-actions.d.ts.map +1 -1
- package/dist/services/issue-recovery-actions.js +9 -0
- package/dist/services/issue-recovery-actions.js.map +1 -1
- package/dist/services/issue-rewake-throttle.d.ts +92 -0
- package/dist/services/issue-rewake-throttle.d.ts.map +1 -0
- package/dist/services/issue-rewake-throttle.js +139 -0
- package/dist/services/issue-rewake-throttle.js.map +1 -0
- package/dist/services/issue-thread-interactions.d.ts +8 -1
- package/dist/services/issue-thread-interactions.d.ts.map +1 -1
- package/dist/services/issue-thread-interactions.js +231 -16
- package/dist/services/issue-thread-interactions.js.map +1 -1
- package/dist/services/issue-visibility.d.ts +4 -0
- package/dist/services/issue-visibility.d.ts.map +1 -0
- package/dist/services/issue-visibility.js +9 -0
- package/dist/services/issue-visibility.js.map +1 -0
- package/dist/services/issues.d.ts +225 -105
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +540 -18
- package/dist/services/issues.js.map +1 -1
- package/dist/services/local-service-supervisor.d.ts +3 -0
- package/dist/services/local-service-supervisor.d.ts.map +1 -1
- package/dist/services/local-service-supervisor.js +51 -0
- package/dist/services/local-service-supervisor.js.map +1 -1
- package/dist/services/pipeline-case-outputs.d.ts.map +1 -1
- package/dist/services/pipeline-case-outputs.js +2 -1
- package/dist/services/pipeline-case-outputs.js.map +1 -1
- package/dist/services/pipelines-aggregation.d.ts.map +1 -1
- package/dist/services/pipelines-aggregation.js +3 -2
- package/dist/services/pipelines-aggregation.js.map +1 -1
- package/dist/services/pipelines.d.ts +165 -165
- package/dist/services/pipelines.d.ts.map +1 -1
- package/dist/services/pipelines.js +6 -4
- package/dist/services/pipelines.js.map +1 -1
- package/dist/services/plugin-database.d.ts +2 -2
- package/dist/services/plugin-environment-driver.d.ts +1 -1
- package/dist/services/plugin-host-services.d.ts.map +1 -1
- package/dist/services/plugin-host-services.js +3 -1
- package/dist/services/plugin-host-services.js.map +1 -1
- package/dist/services/plugin-managed-routines.d.ts +1 -0
- package/dist/services/plugin-managed-routines.d.ts.map +1 -1
- package/dist/services/plugin-registry.d.ts +21 -21
- package/dist/services/productivity-review.d.ts +1 -0
- package/dist/services/productivity-review.d.ts.map +1 -1
- package/dist/services/productivity-review.js +6 -5
- package/dist/services/productivity-review.js.map +1 -1
- package/dist/services/projects.d.ts +9 -9
- package/dist/services/recovery/service.d.ts +82 -54
- package/dist/services/recovery/service.d.ts.map +1 -1
- package/dist/services/recovery/service.js +200 -35
- package/dist/services/recovery/service.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.d.ts +1 -0
- package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -1
- package/dist/services/recovery/successful-run-handoff.js +2 -0
- package/dist/services/recovery/successful-run-handoff.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.test.js +20 -0
- package/dist/services/recovery/successful-run-handoff.test.js.map +1 -1
- package/dist/services/resource-memberships.d.ts +13 -10
- package/dist/services/resource-memberships.d.ts.map +1 -1
- package/dist/services/resource-memberships.js +89 -20
- package/dist/services/resource-memberships.js.map +1 -1
- package/dist/services/responsible-user-denial-run-outcomes.d.ts +60 -0
- package/dist/services/responsible-user-denial-run-outcomes.d.ts.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.js +56 -0
- package/dist/services/responsible-user-denial-run-outcomes.js.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.d.ts +2 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.d.ts.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.js +75 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.js.map +1 -0
- package/dist/services/routines.d.ts +30 -8
- package/dist/services/routines.d.ts.map +1 -1
- package/dist/services/routines.js +241 -18
- package/dist/services/routines.js.map +1 -1
- package/dist/services/run-liveness.d.ts.map +1 -1
- package/dist/services/run-liveness.js +21 -0
- package/dist/services/run-liveness.js.map +1 -1
- package/dist/services/run-log-store.d.ts +1 -0
- package/dist/services/run-log-store.d.ts.map +1 -1
- package/dist/services/run-log-store.js +4 -0
- package/dist/services/run-log-store.js.map +1 -1
- package/dist/services/run-scratch.d.ts +42 -0
- package/dist/services/run-scratch.d.ts.map +1 -0
- package/dist/services/run-scratch.js +111 -0
- package/dist/services/run-scratch.js.map +1 -0
- package/dist/services/run-scratch.test.d.ts +2 -0
- package/dist/services/run-scratch.test.d.ts.map +1 -0
- package/dist/services/run-scratch.test.js +98 -0
- package/dist/services/run-scratch.test.js.map +1 -0
- package/dist/services/secrets.d.ts +1261 -64
- package/dist/services/secrets.d.ts.map +1 -1
- package/dist/services/secrets.js +1089 -96
- package/dist/services/secrets.js.map +1 -1
- package/dist/services/task-watchdogs.d.ts +1 -0
- package/dist/services/task-watchdogs.d.ts.map +1 -1
- package/dist/services/task-watchdogs.js +78 -9
- package/dist/services/task-watchdogs.js.map +1 -1
- package/dist/services/work-timeline.d.ts.map +1 -1
- package/dist/services/work-timeline.js +34 -2
- package/dist/services/work-timeline.js.map +1 -1
- package/dist/services/workspace-runtime-read-model.d.ts +30 -29
- package/dist/services/workspace-runtime-read-model.d.ts.map +1 -1
- package/dist/services/workspace-runtime-read-model.js.map +1 -1
- package/dist/services/workspace-runtime.d.ts +59 -13
- package/dist/services/workspace-runtime.d.ts.map +1 -1
- package/dist/services/workspace-runtime.js +1121 -102
- package/dist/services/workspace-runtime.js.map +1 -1
- package/dist/skills-catalog/catalog/bundled/paperclip-operations/reflection-coach/SKILL.md +202 -0
- package/dist/skills-catalog/catalog/bundled/product/paperclip-capsules/SKILL.md +1 -1
- package/dist/skills-catalog/catalog/bundled/product/wireframe/SKILL.md +1 -1
- package/dist/skills-catalog/catalog/optional/content/release-announcement/SKILL.md +90 -0
- package/dist/skills-catalog/catalog/optional/finance/ramp/SKILL.md +98 -0
- package/dist/skills-catalog/generated/catalog.json +84 -12
- package/dist/ui-branding.d.ts +7 -0
- package/dist/ui-branding.d.ts.map +1 -1
- package/dist/ui-branding.js +8 -2
- package/dist/ui-branding.js.map +1 -1
- package/dist/version.d.ts +14 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +114 -3
- package/dist/version.js.map +1 -1
- package/package.json +19 -20
- package/skills/paperclip/SKILL.md +45 -10
- package/skills/paperclip/references/api-reference.md +246 -1
- package/skills/paperclip/references/cases.md +295 -0
- package/skills/paperclip/references/company-skills.md +1 -1
- package/skills/paperclip-board/SKILL.md +3 -4
- package/skills/paperclip-converting-plans-to-tasks/SKILL.md +3 -7
- package/skills/para-memory-files/SKILL.md +3 -7
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BsFz2IiG.js → abnfDiagram-VRR7QNED-Dtgfy1PK.js} +1 -1
- package/ui-dist/assets/{arc-HZhAwbOs.js → arc-BGdvNvs8.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-BteMjuOn.js → architectureDiagram-ZJ3FMSHR-CK19SQDx.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-Iq66uZjf.js → blockDiagram-677ZJIJ3-Kh4N3XG4.js} +1 -1
- package/ui-dist/assets/{browser-ponyfill-BBsuyZJt.js → browser-ponyfill-B3hXtEFW.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-_nlglj7I.js → c4Diagram-LMCZKHZV-CBHpGrgU.js} +1 -1
- package/ui-dist/assets/channel-4fjB8e8N.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-Cvz7WwSQ.js → chunk-2Q5K7J3B-BdHbqi7k.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-LLubniCS.js → chunk-32BRIVSS-DxtzZzpt.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-BOst5hcr.js → chunk-5VM5RSS4-DMKxBbgu.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-D5WNShVm.js → chunk-EX3LRPZG-BaIOPi4v.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-CA07x4av.js → chunk-JWPE2WC7-DGo3MPmr.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-Clfi83rK.js → chunk-MOJQB5TN-BEhQg0uD.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-BhZ6p7YJ.js → chunk-RYQCIY6F-HPJ6zRpJ.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-CdexJGoV.js → chunk-V7JOEXUC-Bl_6cAJD.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-8vjmBhr1.js → chunk-VR4S4FIN-KMjgGCsw.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-CqmCaplj.js → chunk-XXDRQBXY-oWGAIKjr.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CTXxO5l9.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CTXxO5l9.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-CY-zAygY.js → cose-bilkent-JH36ORCC-Ckqse1pH.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-BQSQ1HKr.js → cynefin-VYW2F7L2-DfZ8Hzqi.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-NJCuFrZL.js → cynefinDiagram-TSTJHNR4-BkDQo9oi.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-DAUPIc8g.js → dagre-VKFMJZFB-ClghXQ6E.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-DvMwJlCi.js → diagram-FQU43EPY-D5a0CwpV.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-DPY0Jz_y.js → diagram-G47NLZAW-hVjYR_xl.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-BpopeEhp.js → diagram-NH7WQ7WH-BlDPgD5Z.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-Qpu05z9X.js → diagram-OA4YK3LP-D0iZKIS3.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-DHnvw9XU.js → diagram-WEI45ONY-m6hrfD0J.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-D61S54_7.js → ebnfDiagram-CCIWWBDH-D4SQYLod.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-pplAN_x2.js → erDiagram-Q63AITRT-Byzb6-I5.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-BaBcbyDq.js → flowDiagram-23GEKE2U-CaTUkFO6.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-BOJSII2b.js → ganttDiagram-NO4QXBWP-DmdMQ3ES.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-aYAaO4a2.js → gitGraphDiagram-IHSO6WYX-Bt-1g2CS.js} +1 -1
- package/ui-dist/assets/{index-CZxYajxF.js → index-2Dk9yO0W.js} +1 -1
- package/ui-dist/assets/{index-B8H0SHw1.js → index-8jLpgFNU.js} +1 -1
- package/ui-dist/assets/{index-3EKATXgc.js → index-AP_iimxs.js} +1 -1
- package/ui-dist/assets/{index-C2JgdjPa.js → index-BNJasQY9.js} +1 -1
- package/ui-dist/assets/{index-BaiskB3o.js → index-BO-aNmyX.js} +1 -1
- package/ui-dist/assets/{index-B5YenxAQ.js → index-BPCzCn1Y.js} +1 -1
- package/ui-dist/assets/{index-Da2kqT1M.js → index-BSLnLTb2.js} +1 -1
- package/ui-dist/assets/{index-By0oeEvz.js → index-CCZ2qgHN.js} +1 -1
- package/ui-dist/assets/{index-bUFCSEgv.js → index-CLZXR1k9.js} +1 -1
- package/ui-dist/assets/{index-CJlw4l9L.js → index-CTNfrE1s.js} +1 -1
- package/ui-dist/assets/{index-TmpY6Xh_.js → index-ChNd25s7.js} +1 -1
- package/ui-dist/assets/{index-BRgdHUNR.js → index-CjHiMOEO.js} +1 -1
- package/ui-dist/assets/{index-DrL6pHSJ.js → index-CmeJQpXZ.js} +1 -1
- package/ui-dist/assets/index-CyuYtn7U.css +1 -0
- package/ui-dist/assets/{index-CKKytnOd.js → index-CzZjM8eh.js} +1 -1
- package/ui-dist/assets/{index-Dceamza_.js → index-D-6T0Aft.js} +1 -1
- package/ui-dist/assets/{index-CjDqB1SX.js → index-DG_7V1pG.js} +1 -1
- package/ui-dist/assets/index-DIKcgxCt.js +676 -0
- package/ui-dist/assets/{index-BKVeJ3Jh.js → index-DiUerHmi.js} +1 -1
- package/ui-dist/assets/{index-C-nKjFZh.js → index-NZ7v5Fc2.js} +1 -1
- package/ui-dist/assets/{index-CvPPuNs1.js → index-Py70EvBg.js} +1 -1
- package/ui-dist/assets/{index-LHkZdu0h.js → index-R3aYn0qM.js} +1 -1
- package/ui-dist/assets/{index-BT8b79Bd.js → index-YXpm7gW-.js} +1 -1
- package/ui-dist/assets/{index-CemenVOf.js → index-n4q3gzki.js} +1 -1
- package/ui-dist/assets/{index-D3ODiUup.js → index-vlZ3GoYY.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-C7_90qGq.js → infoDiagram-FWYZ7A6U-jdi5ciVg.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-T-nPPdTt.js → ishikawaDiagram-FXEZZL3T-CMz5VvYp.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-C3hqQN0R.js → journeyDiagram-5HDEW3XC-DK6dk2R-.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-CPoD9DUm.js → kanban-definition-HUTT4EX6-DX5cZL-D.js} +1 -1
- package/ui-dist/assets/{linear-D-S8yr3Z.js → linear-CI96ABKp.js} +1 -1
- package/ui-dist/assets/{mermaid.core-DhvH8shK.js → mermaid.core-DnsEVfbd.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-CxCIKrKo.js → mindmap-definition-LN4V7U3C-BdDCY7hf.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-D4G-PC6N.js → pegDiagram-2B236MQR-DuY4TF-f.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-N4X8UvqY.js → pieDiagram-ENE6RG2P-bgFw7dm3.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CZohoDln.js → quadrantDiagram-ABIIQ3AL-BCZVWyWS.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-fMGQVhwF.js → railroadDiagram-RFXS5EU6-D9V5_Cgc.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-CK0IuUmk.js → requirementDiagram-TGXJPOKE-CItsya2r.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-pSfb3CqG.js → sankeyDiagram-HTMAVEWB-Cz9IHsZO.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CbhkqIeq.js → sequenceDiagram-DBY2YBRQ-hMSqTiwK.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-B6vqqPx-.js → sizeCapture-X5ZJPWSS-0g9M5wMf.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-DIJf9h5C.js → stateDiagram-2N3HPSRC-F8_YhgWl.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-fZImUBtq.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-DIX_LV8O.js → swimlanes-5IMT3BWC-CCZqPbpE.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-9C1UT51R.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-CD-Pxb_g.js → timeline-definition-FHXFAJF6-BeUIr18x.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-BAZsWL0R.js → vennDiagram-L72KCM5P-D3d5h_G4.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-CnNyfwX5.js → wardleyDiagram-EHGQE667-Bzt-As9A.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-j9YgsD3Y.js → xychartDiagram-FW5EYKEG-C2mly0xf.js} +1 -1
- package/ui-dist/fonts/InterVariable-Italic.woff2 +0 -0
- package/ui-dist/fonts/InterVariable.woff2 +0 -0
- package/ui-dist/fonts/NOTICE.md +17 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/locales/en/common.json +1142 -5
- package/ui-dist/locales/zh-CN/common.json +1140 -3
- package/ui-dist/assets/channel-CsiI2LYw.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CBn1Jqo8.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CBn1Jqo8.js +0 -1
- package/ui-dist/assets/index-BsL8K1vL.css +0 -1
- package/ui-dist/assets/index-D__fTsb2.js +0 -643
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DpwLD7YL.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Cklauutg.js +0 -8
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { and, desc, eq, isNull, sql } from "drizzle-orm";
|
|
2
|
-
import { agents, companies, issues, projects } from "@penclipai/db";
|
|
3
|
-
import { COMPANY_SEARCH_MAX_LIMIT, COMPANY_SEARCH_MAX_OFFSET, COMPANY_SEARCH_MAX_TOKENS, COMPANY_ARTIFACTS_MAX_LIMIT, COMPANY_ARTIFACTS_MAX_QUERY_LENGTH, } from "@penclipai/shared";
|
|
1
|
+
import { and, desc, eq, gte, inArray, isNotNull, isNull, notInArray, or, sql } from "drizzle-orm";
|
|
2
|
+
import { agents, assets, companies, documents, issueAttachments, issueDocuments, issueWorkProducts, issues, projects, } from "@penclipai/db";
|
|
3
|
+
import { COMPANY_SEARCH_MAX_LIMIT, COMPANY_SEARCH_MAX_OFFSET, COMPANY_SEARCH_MAX_TOKENS, COMPANY_SEARCH_UPDATED_WITHIN_OPTIONS, COMPANY_ARTIFACTS_MAX_LIMIT, COMPANY_ARTIFACTS_MAX_QUERY_LENGTH, ISSUE_PRIORITIES, ISSUE_STATUSES, SYSTEM_ISSUE_DOCUMENT_KEYS, } from "@penclipai/shared";
|
|
4
4
|
import { companyArtifactsService } from "./company-artifacts.js";
|
|
5
|
+
import { visibleIssueCondition } from "./issue-visibility.js";
|
|
5
6
|
const MIN_TOKEN_LENGTH = 2;
|
|
6
7
|
const MIN_FUZZY_QUERY_LENGTH = 4;
|
|
7
8
|
const MIN_FUZZY_TOKEN_LENGTH = 4;
|
|
@@ -43,14 +44,10 @@ function sqlTextArray(values) {
|
|
|
43
44
|
return sql `ARRAY[]::text[]`;
|
|
44
45
|
return sql `ARRAY[${sql.join(values.map((value) => sql `${value}`), sql `, `)}]::text[]`;
|
|
45
46
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
FROM unnest(${tokenArray}) AS search_token(value)
|
|
51
|
-
WHERE lower(coalesce(${textExpression}, '')) LIKE '%' || search_token.value || '%' ESCAPE '\\'
|
|
52
|
-
)
|
|
53
|
-
`;
|
|
47
|
+
function sqlUuidArray(values) {
|
|
48
|
+
if (values.length === 0)
|
|
49
|
+
return sql `ARRAY[]::uuid[]`;
|
|
50
|
+
return sql `ARRAY[${sql.join(values.map((value) => sql `${value}`), sql `, `)}]::uuid[]`;
|
|
54
51
|
}
|
|
55
52
|
function noMatchSql() {
|
|
56
53
|
return sql `false`;
|
|
@@ -139,11 +136,211 @@ function issueHref(prefix, issue, suffix = "") {
|
|
|
139
136
|
function matchTerms(normalizedQuery, tokens) {
|
|
140
137
|
return [normalizedQuery, ...tokens].filter((term, index, terms) => term.length > 0 && terms.indexOf(term) === index);
|
|
141
138
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return
|
|
139
|
+
function emptySearchCounts() {
|
|
140
|
+
return { issue: 0, comment: 0, document: 0, artifact: 0, agent: 0, project: 0 };
|
|
141
|
+
}
|
|
142
|
+
function emptyFilterOptionCounts() {
|
|
143
|
+
return {
|
|
144
|
+
status: {},
|
|
145
|
+
priority: {},
|
|
146
|
+
assigneeAgentId: {},
|
|
147
|
+
assigneeUserId: {},
|
|
148
|
+
projectId: {},
|
|
149
|
+
labelId: {},
|
|
150
|
+
updatedWithin: {},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function priorityRank(priority) {
|
|
154
|
+
const index = ISSUE_PRIORITIES.indexOf(priority ?? "");
|
|
155
|
+
return index >= 0 ? index : ISSUE_PRIORITIES.length;
|
|
156
|
+
}
|
|
157
|
+
function updatedWithinStart(value, now = new Date()) {
|
|
158
|
+
if (!value)
|
|
159
|
+
return null;
|
|
160
|
+
const match = /^(\d+)(h|d|w|m)$/.exec(value);
|
|
161
|
+
if (!match)
|
|
162
|
+
return null;
|
|
163
|
+
const amount = Number.parseInt(match[1], 10);
|
|
164
|
+
const unit = match[2];
|
|
165
|
+
const hours = unit === "h" ? amount : unit === "d" ? amount * 24 : unit === "w" ? amount * 24 * 7 : amount * 24 * 30;
|
|
166
|
+
return new Date(now.getTime() - hours * 60 * 60 * 1000);
|
|
167
|
+
}
|
|
168
|
+
function issueOnlyFiltersActive(query) {
|
|
169
|
+
return query.status.length > 0
|
|
170
|
+
|| query.priority.length > 0
|
|
171
|
+
|| query.assigneeAgentId !== undefined
|
|
172
|
+
|| Boolean(query.assigneeUserId)
|
|
173
|
+
|| Boolean(query.projectId)
|
|
174
|
+
|| Boolean(query.labelId)
|
|
175
|
+
|| Boolean(query.updatedWithin)
|
|
176
|
+
|| Boolean(query.updatedAfter);
|
|
177
|
+
}
|
|
178
|
+
function activeIssueFilters(query) {
|
|
179
|
+
const filters = [];
|
|
180
|
+
if (query.status.length > 0)
|
|
181
|
+
filters.push({ key: "status", values: query.status });
|
|
182
|
+
if (query.assigneeAgentId !== undefined)
|
|
183
|
+
filters.push({ key: "assigneeAgentId", values: [query.assigneeAgentId ?? "null"] });
|
|
184
|
+
if (query.assigneeUserId)
|
|
185
|
+
filters.push({ key: "assigneeUserId", values: [query.assigneeUserId] });
|
|
186
|
+
if (query.projectId)
|
|
187
|
+
filters.push({ key: "projectId", values: [query.projectId] });
|
|
188
|
+
if (query.labelId)
|
|
189
|
+
filters.push({ key: "labelId", values: [query.labelId] });
|
|
190
|
+
if (query.priority.length > 0)
|
|
191
|
+
filters.push({ key: "priority", values: query.priority });
|
|
192
|
+
if (query.updatedWithin)
|
|
193
|
+
filters.push({ key: "updatedWithin", values: [query.updatedWithin] });
|
|
194
|
+
if (query.updatedAfter)
|
|
195
|
+
filters.push({ key: "updatedAfter", values: [query.updatedAfter] });
|
|
196
|
+
return filters;
|
|
197
|
+
}
|
|
198
|
+
function queryWithoutFilter(query, key) {
|
|
199
|
+
return {
|
|
200
|
+
...query,
|
|
201
|
+
status: key === "status" ? [] : query.status,
|
|
202
|
+
priority: key === "priority" ? [] : query.priority,
|
|
203
|
+
assigneeAgentId: key === "assigneeAgentId" ? undefined : query.assigneeAgentId,
|
|
204
|
+
assigneeUserId: key === "assigneeUserId" ? undefined : query.assigneeUserId,
|
|
205
|
+
projectId: key === "projectId" ? undefined : query.projectId,
|
|
206
|
+
labelId: key === "labelId" ? undefined : query.labelId,
|
|
207
|
+
updatedWithin: key === "updatedWithin" ? undefined : query.updatedWithin,
|
|
208
|
+
updatedAfter: key === "updatedAfter" ? undefined : query.updatedAfter,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function queryWithoutIssueFilters(query) {
|
|
212
|
+
return {
|
|
213
|
+
...query,
|
|
214
|
+
status: [],
|
|
215
|
+
priority: [],
|
|
216
|
+
assigneeAgentId: undefined,
|
|
217
|
+
assigneeUserId: undefined,
|
|
218
|
+
projectId: undefined,
|
|
219
|
+
labelId: undefined,
|
|
220
|
+
updatedWithin: undefined,
|
|
221
|
+
updatedAfter: undefined,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function issueFilterConditions(companyId, query, omit) {
|
|
225
|
+
const conditions = [];
|
|
226
|
+
if (omit !== "status" && query.status.length > 0) {
|
|
227
|
+
conditions.push(query.status.length === 1 ? eq(issues.status, query.status[0]) : inArray(issues.status, query.status));
|
|
228
|
+
}
|
|
229
|
+
if (omit !== "priority" && query.priority.length > 0) {
|
|
230
|
+
conditions.push(query.priority.length === 1 ? eq(issues.priority, query.priority[0]) : inArray(issues.priority, query.priority));
|
|
231
|
+
}
|
|
232
|
+
if (omit !== "assigneeAgentId" && query.assigneeAgentId !== undefined) {
|
|
233
|
+
conditions.push(query.assigneeAgentId === null ? isNull(issues.assigneeAgentId) : eq(issues.assigneeAgentId, query.assigneeAgentId));
|
|
234
|
+
}
|
|
235
|
+
if (omit !== "assigneeUserId" && query.assigneeUserId) {
|
|
236
|
+
conditions.push(eq(issues.assigneeUserId, query.assigneeUserId));
|
|
237
|
+
}
|
|
238
|
+
if (omit !== "projectId" && query.projectId)
|
|
239
|
+
conditions.push(eq(issues.projectId, query.projectId));
|
|
240
|
+
if (omit !== "labelId" && query.labelId) {
|
|
241
|
+
conditions.push(sql `
|
|
242
|
+
EXISTS (
|
|
243
|
+
SELECT 1
|
|
244
|
+
FROM issue_labels search_filter_labels
|
|
245
|
+
WHERE search_filter_labels.company_id = ${companyId}
|
|
246
|
+
AND search_filter_labels.issue_id = ${issues.id}
|
|
247
|
+
AND search_filter_labels.label_id = ${query.labelId}
|
|
248
|
+
)
|
|
249
|
+
`);
|
|
250
|
+
}
|
|
251
|
+
if (omit !== "updatedWithin") {
|
|
252
|
+
const updatedWithin = updatedWithinStart(query.updatedWithin);
|
|
253
|
+
if (updatedWithin)
|
|
254
|
+
conditions.push(gte(issues.updatedAt, updatedWithin));
|
|
255
|
+
}
|
|
256
|
+
if (omit !== "updatedAfter" && query.updatedAfter) {
|
|
257
|
+
conditions.push(gte(issues.updatedAt, new Date(query.updatedAfter)));
|
|
258
|
+
}
|
|
259
|
+
return conditions;
|
|
260
|
+
}
|
|
261
|
+
// Facet conditions expressed against the `m` alias of the aggregate
|
|
262
|
+
// matched-issues CTE (plain columns, no drizzle table references).
|
|
263
|
+
function matchedFacetConditions(companyId, query, omit) {
|
|
264
|
+
const conditions = [];
|
|
265
|
+
if (omit !== "status" && query.status.length > 0) {
|
|
266
|
+
conditions.push(sql `m.status = ANY(${sqlTextArray(query.status)})`);
|
|
267
|
+
}
|
|
268
|
+
if (omit !== "priority" && query.priority.length > 0) {
|
|
269
|
+
conditions.push(sql `m.priority = ANY(${sqlTextArray(query.priority)})`);
|
|
270
|
+
}
|
|
271
|
+
if (omit !== "assigneeAgentId" && query.assigneeAgentId !== undefined) {
|
|
272
|
+
conditions.push(query.assigneeAgentId === null
|
|
273
|
+
? sql `m.assignee_agent_id IS NULL`
|
|
274
|
+
: sql `m.assignee_agent_id = ${query.assigneeAgentId}`);
|
|
275
|
+
}
|
|
276
|
+
if (omit !== "assigneeUserId" && query.assigneeUserId) {
|
|
277
|
+
conditions.push(sql `m.assignee_user_id = ${query.assigneeUserId}`);
|
|
278
|
+
}
|
|
279
|
+
if (omit !== "projectId" && query.projectId) {
|
|
280
|
+
conditions.push(sql `m.project_id = ${query.projectId}`);
|
|
281
|
+
}
|
|
282
|
+
if (omit !== "labelId" && query.labelId) {
|
|
283
|
+
conditions.push(sql `
|
|
284
|
+
EXISTS (
|
|
285
|
+
SELECT 1
|
|
286
|
+
FROM issue_labels facet_filter_labels
|
|
287
|
+
WHERE facet_filter_labels.company_id = ${companyId}
|
|
288
|
+
AND facet_filter_labels.issue_id = m.id
|
|
289
|
+
AND facet_filter_labels.label_id = ${query.labelId}
|
|
290
|
+
)
|
|
291
|
+
`);
|
|
292
|
+
}
|
|
293
|
+
if (omit !== "updatedWithin") {
|
|
294
|
+
const updatedWithin = updatedWithinStart(query.updatedWithin);
|
|
295
|
+
// ISO strings: raw sql params bypass drizzle's column-level Date mapping.
|
|
296
|
+
if (updatedWithin)
|
|
297
|
+
conditions.push(sql `m.updated_at >= ${updatedWithin.toISOString()}::timestamptz`);
|
|
298
|
+
}
|
|
299
|
+
if (omit !== "updatedAfter" && query.updatedAfter) {
|
|
300
|
+
conditions.push(sql `m.updated_at >= ${new Date(query.updatedAfter).toISOString()}::timestamptz`);
|
|
301
|
+
}
|
|
302
|
+
return conditions;
|
|
303
|
+
}
|
|
304
|
+
function stripInternalSortFields(result) {
|
|
305
|
+
const { sortCreatedAt: _sortCreatedAt, sortPriorityRank: _sortPriorityRank, ...publicResult } = result;
|
|
306
|
+
return publicResult;
|
|
307
|
+
}
|
|
308
|
+
function compareSearchResults(sort) {
|
|
309
|
+
return (left, right) => {
|
|
310
|
+
if (sort === "updated") {
|
|
311
|
+
const updated = (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
|
|
312
|
+
if (updated !== 0)
|
|
313
|
+
return updated;
|
|
314
|
+
if (right.score !== left.score)
|
|
315
|
+
return right.score - left.score;
|
|
316
|
+
}
|
|
317
|
+
else if (sort === "created") {
|
|
318
|
+
const created = (right.sortCreatedAt ?? "").localeCompare(left.sortCreatedAt ?? "");
|
|
319
|
+
if (created !== 0)
|
|
320
|
+
return created;
|
|
321
|
+
const updated = (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
|
|
322
|
+
if (updated !== 0)
|
|
323
|
+
return updated;
|
|
324
|
+
}
|
|
325
|
+
else if (sort === "priority") {
|
|
326
|
+
const priority = left.sortPriorityRank - right.sortPriorityRank;
|
|
327
|
+
if (priority !== 0)
|
|
328
|
+
return priority;
|
|
329
|
+
const updated = (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
|
|
330
|
+
if (updated !== 0)
|
|
331
|
+
return updated;
|
|
332
|
+
if (right.score !== left.score)
|
|
333
|
+
return right.score - left.score;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
if (right.score !== left.score)
|
|
337
|
+
return right.score - left.score;
|
|
338
|
+
const updated = (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
|
|
339
|
+
if (updated !== 0)
|
|
340
|
+
return updated;
|
|
341
|
+
}
|
|
342
|
+
return right.id.localeCompare(left.id);
|
|
343
|
+
};
|
|
147
344
|
}
|
|
148
345
|
function scopeIncludesIssues(scope) {
|
|
149
346
|
return scope === "all" || scope === "issues" || scope === "comments" || scope === "documents";
|
|
@@ -157,15 +354,6 @@ function scopeIncludesArtifacts(scope) {
|
|
|
157
354
|
function scopeIncludesProjects(scope) {
|
|
158
355
|
return scope === "all" || scope === "projects";
|
|
159
356
|
}
|
|
160
|
-
function issueSearchCondition(scope, input) {
|
|
161
|
-
if (scope === "comments")
|
|
162
|
-
return input.commentMatch;
|
|
163
|
-
if (scope === "documents")
|
|
164
|
-
return input.documentMatch;
|
|
165
|
-
if (scope === "issues")
|
|
166
|
-
return sql `(${input.issueTextMatch} OR ${input.fuzzyMatch})`;
|
|
167
|
-
return sql `(${input.issueTextMatch} OR ${input.commentMatch} OR ${input.documentMatch} OR ${input.fuzzyMatch})`;
|
|
168
|
-
}
|
|
169
357
|
function selectPrimarySnippets(row, normalizedQuery, tokens) {
|
|
170
358
|
const terms = matchTerms(normalizedQuery, tokens);
|
|
171
359
|
const matchedFields = new Set(row.matchedFields ?? []);
|
|
@@ -253,6 +441,7 @@ function artifactResult(artifact, normalizedQuery, tokens) {
|
|
|
253
441
|
description: [artifact.previewText, artifact.issue.identifier, artifact.issue.title, artifact.project?.name]
|
|
254
442
|
.filter(Boolean)
|
|
255
443
|
.join(" "),
|
|
444
|
+
createdAt: new Date(artifact.updatedAt),
|
|
256
445
|
updatedAt: new Date(artifact.updatedAt),
|
|
257
446
|
}, normalizedQuery, tokens);
|
|
258
447
|
return {
|
|
@@ -270,9 +459,9 @@ function artifactResult(artifact, normalizedQuery, tokens) {
|
|
|
270
459
|
previewImageUrl: artifact.mediaKind === "image" ? artifact.contentPath : null,
|
|
271
460
|
};
|
|
272
461
|
}
|
|
273
|
-
function simpleTextCondition(fields, containsPattern,
|
|
274
|
-
const phraseConditions = fields.map((field) => sql `
|
|
275
|
-
const tokenConditions = fields.map((field) =>
|
|
462
|
+
function simpleTextCondition(fields, containsPattern, tokenPatternArray) {
|
|
463
|
+
const phraseConditions = fields.map((field) => sql `coalesce(${field}, '') ILIKE ${containsPattern}`);
|
|
464
|
+
const tokenConditions = fields.map((field) => sql `coalesce(${field}, '') ILIKE ANY(${tokenPatternArray})`);
|
|
276
465
|
return sql `(${sql.join([...phraseConditions, ...tokenConditions], sql ` OR `)})`;
|
|
277
466
|
}
|
|
278
467
|
export function companySearchBranchFetchLimit(limit, offset = 0) {
|
|
@@ -284,85 +473,111 @@ export function companySearchService(db) {
|
|
|
284
473
|
return {
|
|
285
474
|
search: async (companyId, query) => {
|
|
286
475
|
const normalizedQuery = normalizeQuery(query.q);
|
|
476
|
+
const hasSearchText = normalizedQuery.length > 0;
|
|
287
477
|
const tokens = tokenizeQuery(normalizedQuery);
|
|
288
478
|
const scope = query.scope;
|
|
479
|
+
const sort = query.sort;
|
|
289
480
|
const limit = query.limit;
|
|
290
481
|
const offset = query.offset;
|
|
291
|
-
|
|
292
|
-
if (normalizedQuery.length === 0) {
|
|
482
|
+
if (!hasSearchText && !issueOnlyFiltersActive(query)) {
|
|
293
483
|
return {
|
|
294
484
|
query: query.q,
|
|
295
485
|
normalizedQuery,
|
|
296
486
|
scope,
|
|
487
|
+
sort,
|
|
297
488
|
limit,
|
|
298
489
|
offset,
|
|
299
490
|
results: [],
|
|
300
|
-
countsByType:
|
|
491
|
+
countsByType: emptySearchCounts(),
|
|
492
|
+
filterOptionCounts: emptyFilterOptionCounts(),
|
|
493
|
+
zeroResults: null,
|
|
301
494
|
hasMore: false,
|
|
302
495
|
};
|
|
303
496
|
}
|
|
304
|
-
const company = await db
|
|
305
|
-
.select({ issuePrefix: companies.issuePrefix })
|
|
306
|
-
.from(companies)
|
|
307
|
-
.where(eq(companies.id, companyId))
|
|
308
|
-
.then((rows) => rows[0] ?? null);
|
|
309
|
-
const prefix = routePrefix(company?.issuePrefix);
|
|
310
497
|
const fetchLimit = companySearchBranchFetchLimit(limit, offset);
|
|
311
498
|
const escapedTokens = tokens.map(escapeLikePattern);
|
|
312
|
-
|
|
499
|
+
// LIKE/ILIKE both treat backslash as the default escape character, so the
|
|
500
|
+
// escaped tokens stay literal inside ILIKE ANY(...) patterns too.
|
|
501
|
+
const tokenPatterns = escapedTokens.map((token) => `%${token}%`);
|
|
502
|
+
const tokenPatternArray = sqlTextArray(tokenPatterns);
|
|
313
503
|
const fuzzyTokens = fuzzyEligibleTokens(tokens);
|
|
314
504
|
const fuzzyTokenArray = sqlTextArray(fuzzyTokens);
|
|
315
505
|
const escapedQuery = escapeLikePattern(normalizedQuery);
|
|
316
|
-
const containsPattern = `%${escapedQuery}
|
|
317
|
-
const startsWithPattern = `${escapedQuery}
|
|
318
|
-
const fuzzyEnabled = normalizedQuery.length >= MIN_FUZZY_QUERY_LENGTH && !/[\\%_]/.test(normalizedQuery);
|
|
506
|
+
const containsPattern = hasSearchText ? `%${escapedQuery}%` : "__paperclip_no_match__";
|
|
507
|
+
const startsWithPattern = hasSearchText ? `${escapedQuery}%` : "__paperclip_no_match__";
|
|
508
|
+
const fuzzyEnabled = hasSearchText && normalizedQuery.length >= MIN_FUZZY_QUERY_LENGTH && !/[\\%_]/.test(normalizedQuery);
|
|
319
509
|
const fuzzyTokensEnabled = fuzzyEnabled && fuzzyTokens.length > 0;
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
const
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
510
|
+
const tokenCount = tokens.length;
|
|
511
|
+
// --- shared match expressions against the `issues` table -------------
|
|
512
|
+
// Raw-column ILIKE keeps the predicates compatible with the existing
|
|
513
|
+
// pg_trgm GIN indexes (lower(col) LIKE expressions cannot use them).
|
|
514
|
+
const titlePhraseMatch = hasSearchText ? sql `issues.title ILIKE ${containsPattern}` : noMatchSql();
|
|
515
|
+
const titleStartsWith = hasSearchText ? sql `issues.title ILIKE ${startsWithPattern}` : noMatchSql();
|
|
516
|
+
const titleExactMatch = hasSearchText ? sql `lower(issues.title) = ${normalizedQuery}` : noMatchSql();
|
|
517
|
+
const identifierPhraseMatch = hasSearchText ? sql `coalesce(issues.identifier, '') ILIKE ${containsPattern}` : noMatchSql();
|
|
518
|
+
const identifierStartsWith = hasSearchText ? sql `coalesce(issues.identifier, '') ILIKE ${startsWithPattern}` : noMatchSql();
|
|
519
|
+
const identifierExactMatch = hasSearchText ? sql `lower(coalesce(issues.identifier, '')) = ${normalizedQuery}` : noMatchSql();
|
|
520
|
+
const descriptionPhraseMatch = hasSearchText ? sql `coalesce(issues.description, '') ILIKE ${containsPattern}` : noMatchSql();
|
|
521
|
+
const titleTokenMatch = tokenCount > 0 ? sql `issues.title ILIKE ANY(${tokenPatternArray})` : noMatchSql();
|
|
522
|
+
const identifierTokenMatch = tokenCount > 0 ? sql `coalesce(issues.identifier, '') ILIKE ANY(${tokenPatternArray})` : noMatchSql();
|
|
523
|
+
const descriptionTokenMatch = tokenCount > 0 ? sql `coalesce(issues.description, '') ILIKE ANY(${tokenPatternArray})` : noMatchSql();
|
|
524
|
+
// Comment/document matches are computed once per request into tagged
|
|
525
|
+
// CTEs (issue_id, ord) where ord 1 is the phrase pattern and ord k+1 is
|
|
526
|
+
// token k. Flags and per-token coverage become cheap hashed IN probes
|
|
527
|
+
// against those sets instead of per-issue-row correlated subqueries.
|
|
528
|
+
// Single-pattern queries stay a bare `col ILIKE pattern` so the pg_trgm
|
|
529
|
+
// GIN indexes can bitmap-scan them; multi-pattern queries use one tagged
|
|
530
|
+
// pass over the table (an OR/ANY form would seq-scan anyway).
|
|
531
|
+
const matchPatterns = hasSearchText
|
|
532
|
+
? [containsPattern, ...tokenPatterns.filter((pattern) => pattern !== containsPattern)]
|
|
533
|
+
: [];
|
|
534
|
+
const matchPatternOrdinal = (pattern) => matchPatterns.indexOf(pattern) + 1;
|
|
535
|
+
const matchPatternArray = sqlTextArray(matchPatterns);
|
|
536
|
+
const commentMatchesCte = !hasSearchText
|
|
537
|
+
? sql `SELECT NULL::uuid AS issue_id, 0 AS ord WHERE false`
|
|
538
|
+
: matchPatterns.length === 1
|
|
539
|
+
? sql `
|
|
540
|
+
SELECT search_comments.issue_id, 1 AS ord
|
|
541
|
+
FROM issue_comments search_comments
|
|
542
|
+
WHERE search_comments.company_id = ${companyId}
|
|
543
|
+
AND search_comments.deleted_at IS NULL
|
|
544
|
+
AND search_comments.body ILIKE ${matchPatterns[0]}
|
|
545
|
+
GROUP BY 1, 2
|
|
546
|
+
`
|
|
547
|
+
: sql `
|
|
548
|
+
SELECT search_comments.issue_id, pat.ord::int AS ord
|
|
549
|
+
FROM issue_comments search_comments
|
|
550
|
+
INNER JOIN unnest(${matchPatternArray}) WITH ORDINALITY AS pat(pattern, ord)
|
|
551
|
+
ON search_comments.body ILIKE pat.pattern
|
|
552
|
+
WHERE search_comments.company_id = ${companyId}
|
|
553
|
+
AND search_comments.deleted_at IS NULL
|
|
554
|
+
GROUP BY 1, 2
|
|
555
|
+
`;
|
|
556
|
+
// Documents get one UNION ALL arm per pattern (each arm a bare
|
|
557
|
+
// `col ILIKE pattern`) so the planner can pick a pg_trgm bitmap scan per
|
|
558
|
+
// pattern; latest_body is large enough that skipping the seq scan for
|
|
559
|
+
// selective patterns dwarfs the duplicate-recheck cost on common ones.
|
|
560
|
+
const documentMatchesCte = !hasSearchText
|
|
561
|
+
? sql `SELECT NULL::uuid AS issue_id, 0 AS ord WHERE false`
|
|
562
|
+
: sql.join(matchPatterns.map((pattern, index) => sql `
|
|
563
|
+
SELECT search_issue_documents.issue_id, ${index + 1}::int AS ord
|
|
564
|
+
FROM issue_documents search_issue_documents
|
|
565
|
+
INNER JOIN documents search_documents
|
|
566
|
+
ON search_documents.id = search_issue_documents.document_id
|
|
567
|
+
AND search_documents.company_id = search_issue_documents.company_id
|
|
568
|
+
WHERE search_issue_documents.company_id = ${companyId}
|
|
569
|
+
AND (
|
|
570
|
+
search_documents.title ILIKE ${pattern}
|
|
571
|
+
OR search_documents.latest_body ILIKE ${pattern}
|
|
572
|
+
)
|
|
573
|
+
GROUP BY 1, 2
|
|
574
|
+
`), sql ` UNION ALL `);
|
|
575
|
+
const commentMatch = hasSearchText
|
|
576
|
+
? sql `issues.id IN (SELECT comment_matches.issue_id FROM comment_matches)`
|
|
577
|
+
: noMatchSql();
|
|
578
|
+
const documentMatch = hasSearchText
|
|
579
|
+
? sql `issues.id IN (SELECT document_matches.issue_id FROM document_matches)`
|
|
580
|
+
: noMatchSql();
|
|
366
581
|
// Each query token (length >= MIN_FUZZY_TOKEN_LENGTH) must have at least
|
|
367
582
|
// one title word within Levenshtein edit distance. This handles typos
|
|
368
583
|
// like "serach" -> "search" (transposition) and "mibile" -> "mobile"
|
|
@@ -382,7 +597,7 @@ export function companySearchService(db) {
|
|
|
382
597
|
SELECT bool_and(
|
|
383
598
|
EXISTS (
|
|
384
599
|
SELECT 1
|
|
385
|
-
FROM regexp_split_to_table(lower(
|
|
600
|
+
FROM regexp_split_to_table(lower(issues.title), '[^a-z0-9]+') AS title_word(value)
|
|
386
601
|
WHERE length(title_word.value) >= ${fuzzyMinTitleWordLengthExpr}
|
|
387
602
|
AND levenshtein_less_equal(qt.value, title_word.value, ${fuzzyMaxEditsExpr}) <= ${fuzzyMaxEditsExpr}
|
|
388
603
|
)
|
|
@@ -392,236 +607,542 @@ export function companySearchService(db) {
|
|
|
392
607
|
`
|
|
393
608
|
: noMatchSql();
|
|
394
609
|
const fuzzyIdentifierMatch = fuzzyEnabled
|
|
395
|
-
? sql `similarity(lower(coalesce(
|
|
610
|
+
? sql `similarity(lower(coalesce(issues.identifier, '')), ${normalizedQuery}) >= ${FUZZY_IDENTIFIER_SIMILARITY_THRESHOLD}`
|
|
396
611
|
: noMatchSql();
|
|
612
|
+
const issueTextMatch = sql `(
|
|
613
|
+
${titlePhraseMatch}
|
|
614
|
+
OR ${identifierPhraseMatch}
|
|
615
|
+
OR ${descriptionPhraseMatch}
|
|
616
|
+
OR ${titleTokenMatch}
|
|
617
|
+
OR ${identifierTokenMatch}
|
|
618
|
+
OR ${descriptionTokenMatch}
|
|
619
|
+
)`;
|
|
397
620
|
const fuzzyMatch = sql `(${fuzzyTokenTitleMatch} OR ${fuzzyIdentifierMatch})`;
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
621
|
+
const anySearchMatch = sql `(${issueTextMatch} OR ${commentMatch} OR ${documentMatch} OR ${fuzzyMatch})`;
|
|
622
|
+
const issueFilters = issueFilterConditions(companyId, query);
|
|
623
|
+
const hasIssueOnlyFilters = issueOnlyFiltersActive(query);
|
|
624
|
+
// Scope conditions over precomputed flag columns (alias-qualified).
|
|
625
|
+
function flagTextMatch(alias) {
|
|
626
|
+
return sql `(
|
|
627
|
+
${sql.raw(alias)}.title_phrase OR ${sql.raw(alias)}.ident_phrase OR ${sql.raw(alias)}.desc_phrase
|
|
628
|
+
OR ${sql.raw(alias)}.title_token OR ${sql.raw(alias)}.ident_token OR ${sql.raw(alias)}.desc_token
|
|
629
|
+
)`;
|
|
630
|
+
}
|
|
631
|
+
function flagFuzzyMatch(alias) {
|
|
632
|
+
return sql `(${sql.raw(alias)}.fuzzy_title OR ${sql.raw(alias)}.fuzzy_ident)`;
|
|
633
|
+
}
|
|
634
|
+
function flagScopeCondition(alias, forScope) {
|
|
635
|
+
if (!hasSearchText) {
|
|
636
|
+
return forScope === "comments" || forScope === "documents" ? noMatchSql() : sql `true`;
|
|
637
|
+
}
|
|
638
|
+
if (forScope === "comments")
|
|
639
|
+
return sql `${sql.raw(alias)}.comment_match`;
|
|
640
|
+
if (forScope === "documents")
|
|
641
|
+
return sql `${sql.raw(alias)}.document_match`;
|
|
642
|
+
if (forScope === "issues")
|
|
643
|
+
return sql `(${flagTextMatch(alias)} OR ${flagFuzzyMatch(alias)})`;
|
|
644
|
+
return sql `(${flagTextMatch(alias)} OR ${sql.raw(alias)}.comment_match OR ${sql.raw(alias)}.document_match OR ${flagFuzzyMatch(alias)})`;
|
|
645
|
+
}
|
|
646
|
+
async function fetchIssueSearchData() {
|
|
647
|
+
const filtersActive = activeIssueFilters(query);
|
|
648
|
+
const scopeCond = flagScopeCondition("m", scope);
|
|
649
|
+
const optionCond = scopeIncludesIssues(scope) ? scopeCond : flagScopeCondition("m", "all");
|
|
650
|
+
const titleCond = hasSearchText ? sql `(${flagTextMatch("m")} OR ${flagFuzzyMatch("m")})` : sql `true`;
|
|
651
|
+
const facetsAll = matchedFacetConditions(companyId, query);
|
|
652
|
+
const branchWhere = (conditions) => conditions.length > 0 ? sql `WHERE ${sql.join(conditions, sql ` AND `)}` : sql ``;
|
|
653
|
+
// Count branches must match the result branch's column list; the
|
|
654
|
+
// trailing NULLs pad the issue data columns.
|
|
655
|
+
const countTail = sql `, NULL::uuid, NULL::text, NULL::text, NULL::text, NULL::text, NULL::text, NULL::uuid, NULL::text, NULL::uuid, NULL::timestamptz, NULL::timestamptz, NULL::double precision, NULL::text[]`;
|
|
656
|
+
const branches = [];
|
|
657
|
+
const wantResultRows = scopeIncludesIssues(scope)
|
|
658
|
+
&& !(!hasSearchText && (scope === "comments" || scope === "documents"));
|
|
659
|
+
if (wantResultRows) {
|
|
660
|
+
const allTokensBonus = tokenCount > 0
|
|
661
|
+
? sql `CASE WHEN m.token_coverage = ${tokenCount} THEN 260 ELSE 0 END`
|
|
662
|
+
: sql `0`;
|
|
663
|
+
const scoreSql = sql `(
|
|
664
|
+
CASE WHEN m.ident_exact THEN 1200 ELSE 0 END
|
|
665
|
+
+ CASE WHEN m.ident_starts THEN 700 ELSE 0 END
|
|
666
|
+
+ CASE WHEN m.title_exact THEN 900 ELSE 0 END
|
|
667
|
+
+ CASE WHEN m.title_starts THEN 550 ELSE 0 END
|
|
668
|
+
+ CASE WHEN m.title_phrase THEN 350 ELSE 0 END
|
|
669
|
+
+ CASE WHEN m.ident_phrase THEN 320 ELSE 0 END
|
|
670
|
+
+ CASE WHEN m.comment_match THEN 180 ELSE 0 END
|
|
671
|
+
+ CASE WHEN m.document_match THEN 170 ELSE 0 END
|
|
672
|
+
+ CASE WHEN m.desc_phrase THEN 120 ELSE 0 END
|
|
673
|
+
+ ${allTokensBonus}
|
|
674
|
+
+ (m.token_coverage * 70)
|
|
675
|
+
+ CASE WHEN (m.fuzzy_title OR m.fuzzy_ident) THEN 110 ELSE 0 END
|
|
676
|
+
+ CASE m.status WHEN 'done' THEN 0 WHEN 'cancelled' THEN -30 ELSE 20 END
|
|
677
|
+
)::double precision`;
|
|
678
|
+
const priorityOrderSql = sql `CASE m.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END`;
|
|
679
|
+
const orderBySql = sort === "updated"
|
|
680
|
+
? sql `m.updated_at DESC, score DESC, m.id DESC`
|
|
681
|
+
: sort === "created"
|
|
682
|
+
? sql `m.created_at DESC, m.updated_at DESC, m.id DESC`
|
|
683
|
+
: sort === "priority"
|
|
684
|
+
? sql `${priorityOrderSql} ASC, m.updated_at DESC, score DESC, m.id DESC`
|
|
685
|
+
: sql `score DESC, m.updated_at DESC, m.id DESC`;
|
|
686
|
+
branches.push(sql `(
|
|
687
|
+
SELECT
|
|
688
|
+
'result'::text AS kind,
|
|
689
|
+
NULL::text AS value,
|
|
690
|
+
0 AS count,
|
|
691
|
+
m.id,
|
|
692
|
+
m.identifier,
|
|
693
|
+
m.title,
|
|
694
|
+
m.description,
|
|
695
|
+
m.status,
|
|
696
|
+
m.priority,
|
|
697
|
+
m.assignee_agent_id AS "assigneeAgentId",
|
|
698
|
+
m.assignee_user_id AS "assigneeUserId",
|
|
699
|
+
m.project_id AS "projectId",
|
|
700
|
+
m.created_at AS "createdAt",
|
|
701
|
+
m.updated_at AS "updatedAt",
|
|
702
|
+
${scoreSql} AS score,
|
|
703
|
+
array_remove(ARRAY[
|
|
704
|
+
CASE WHEN m.ident_phrase OR m.ident_token OR m.fuzzy_ident THEN 'identifier' END,
|
|
705
|
+
CASE WHEN m.title_phrase OR m.title_token OR m.fuzzy_title THEN 'title' END,
|
|
706
|
+
CASE WHEN m.desc_phrase OR m.desc_token THEN 'description' END,
|
|
707
|
+
CASE WHEN m.comment_match THEN 'comment' END,
|
|
708
|
+
CASE WHEN m.document_match THEN 'document' END
|
|
709
|
+
], NULL)::text[] AS "matchedFields"
|
|
710
|
+
FROM matched m
|
|
711
|
+
${branchWhere([...facetsAll, scopeCond])}
|
|
712
|
+
ORDER BY ${orderBySql}
|
|
713
|
+
LIMIT ${fetchLimit}
|
|
714
|
+
)`);
|
|
715
|
+
}
|
|
716
|
+
if (scope === "all" || scope === "issues") {
|
|
717
|
+
branches.push(sql `SELECT 'type:issue' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([...facetsAll, titleCond])}`);
|
|
718
|
+
}
|
|
719
|
+
if (hasSearchText && (scope === "all" || scope === "comments")) {
|
|
720
|
+
branches.push(sql `SELECT 'type:comment' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([...facetsAll, sql `m.comment_match`])}`);
|
|
721
|
+
}
|
|
722
|
+
if (hasSearchText && (scope === "all" || scope === "documents")) {
|
|
723
|
+
branches.push(sql `SELECT 'type:document' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([...facetsAll, sql `m.document_match`])}`);
|
|
724
|
+
}
|
|
725
|
+
const facetBranch = (kind, valueSql, omit, extra = []) => sql `
|
|
726
|
+
SELECT ${kind}::text AS kind, ${valueSql}::text AS value, count(*)::int AS count ${countTail}
|
|
727
|
+
FROM matched m
|
|
728
|
+
${branchWhere([optionCond, ...matchedFacetConditions(companyId, query, omit), ...extra])}
|
|
729
|
+
GROUP BY 2
|
|
730
|
+
`;
|
|
731
|
+
branches.push(facetBranch("facet:status", sql `m.status`, "status"));
|
|
732
|
+
branches.push(facetBranch("facet:priority", sql `m.priority`, "priority"));
|
|
733
|
+
branches.push(facetBranch("facet:assigneeAgentId", sql `m.assignee_agent_id`, "assigneeAgentId", [sql `m.assignee_agent_id IS NOT NULL`]));
|
|
734
|
+
branches.push(facetBranch("facet:assigneeUserId", sql `m.assignee_user_id`, "assigneeUserId", [sql `m.assignee_user_id IS NOT NULL`]));
|
|
735
|
+
branches.push(facetBranch("facet:projectId", sql `m.project_id`, "projectId", [sql `m.project_id IS NOT NULL`]));
|
|
736
|
+
branches.push(sql `
|
|
737
|
+
SELECT 'facet:labelId' AS kind, matched_labels.label_id::text AS value, count(DISTINCT m.id)::int AS count ${countTail}
|
|
738
|
+
FROM matched m
|
|
739
|
+
INNER JOIN issue_labels matched_labels
|
|
740
|
+
ON matched_labels.issue_id = m.id
|
|
741
|
+
AND matched_labels.company_id = ${companyId}
|
|
742
|
+
${branchWhere([optionCond, ...matchedFacetConditions(companyId, query, "labelId")])}
|
|
743
|
+
GROUP BY 2
|
|
744
|
+
`);
|
|
745
|
+
const updatedBaseQuery = { ...query, updatedWithin: undefined, updatedAfter: undefined };
|
|
746
|
+
const updatedBaseFacets = matchedFacetConditions(companyId, updatedBaseQuery);
|
|
747
|
+
for (const option of COMPANY_SEARCH_UPDATED_WITHIN_OPTIONS) {
|
|
748
|
+
const start = updatedWithinStart(option);
|
|
749
|
+
if (!start)
|
|
750
|
+
continue;
|
|
751
|
+
branches.push(sql `
|
|
752
|
+
SELECT 'facet:updatedWithin'::text AS kind, ${option}::text AS value, count(*)::int AS count ${countTail}
|
|
753
|
+
FROM matched m
|
|
754
|
+
${branchWhere([optionCond, ...updatedBaseFacets, sql `m.updated_at >= ${start.toISOString()}::timestamptz`])}
|
|
755
|
+
`);
|
|
756
|
+
}
|
|
757
|
+
if (scopeIncludesIssues(scope)) {
|
|
758
|
+
branches.push(sql `SELECT 'total:current' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([scopeCond, ...facetsAll])}`);
|
|
759
|
+
if (filtersActive.length > 0) {
|
|
760
|
+
branches.push(sql `SELECT 'total:unfiltered' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([scopeCond])}`);
|
|
761
|
+
for (const filter of filtersActive) {
|
|
762
|
+
branches.push(sql `
|
|
763
|
+
SELECT ${`total:omit:${filter.key}`}::text AS kind, NULL::text AS value, count(*)::int AS count ${countTail}
|
|
764
|
+
FROM matched m
|
|
765
|
+
${branchWhere([scopeCond, ...matchedFacetConditions(companyId, query, filter.key)])}
|
|
766
|
+
`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
// Per-token coverage counts matches across issue text and the tagged
|
|
771
|
+
// comment/document match sets (hashed IN probes, one set per token).
|
|
772
|
+
const coverageSql = tokenCount > 0
|
|
773
|
+
? sql `(${sql.join(tokens.map((_, index) => {
|
|
774
|
+
const pattern = tokenPatterns[index];
|
|
775
|
+
const ord = matchPatternOrdinal(pattern);
|
|
776
|
+
return sql `(CASE WHEN
|
|
777
|
+
issues.title ILIKE ${pattern}
|
|
778
|
+
OR coalesce(issues.identifier, '') ILIKE ${pattern}
|
|
779
|
+
OR coalesce(issues.description, '') ILIKE ${pattern}
|
|
780
|
+
OR issues.id IN (SELECT comment_matches.issue_id FROM comment_matches WHERE comment_matches.ord = ${ord})
|
|
781
|
+
OR issues.id IN (SELECT document_matches.issue_id FROM document_matches WHERE document_matches.ord = ${ord})
|
|
782
|
+
THEN 1 ELSE 0 END)`;
|
|
783
|
+
}), sql ` + `)})`
|
|
784
|
+
: sql `0`;
|
|
785
|
+
const matchedWhere = hasSearchText ? sql ` AND ${anySearchMatch}` : sql ``;
|
|
786
|
+
const resultRows = await db.execute(sql `
|
|
787
|
+
WITH comment_matches AS MATERIALIZED (${commentMatchesCte}),
|
|
788
|
+
document_matches AS MATERIALIZED (${documentMatchesCte}),
|
|
789
|
+
matched AS MATERIALIZED (
|
|
790
|
+
SELECT
|
|
791
|
+
issues.id,
|
|
792
|
+
issues.identifier,
|
|
793
|
+
issues.title,
|
|
794
|
+
issues.description,
|
|
795
|
+
issues.status,
|
|
796
|
+
issues.priority,
|
|
797
|
+
issues.assignee_agent_id,
|
|
798
|
+
issues.assignee_user_id,
|
|
799
|
+
issues.project_id,
|
|
800
|
+
issues.created_at,
|
|
801
|
+
issues.updated_at,
|
|
802
|
+
${titlePhraseMatch} AS title_phrase,
|
|
803
|
+
${titleStartsWith} AS title_starts,
|
|
804
|
+
${titleExactMatch} AS title_exact,
|
|
805
|
+
${identifierPhraseMatch} AS ident_phrase,
|
|
806
|
+
${identifierStartsWith} AS ident_starts,
|
|
807
|
+
${identifierExactMatch} AS ident_exact,
|
|
808
|
+
${descriptionPhraseMatch} AS desc_phrase,
|
|
809
|
+
${titleTokenMatch} AS title_token,
|
|
810
|
+
${identifierTokenMatch} AS ident_token,
|
|
811
|
+
${descriptionTokenMatch} AS desc_token,
|
|
812
|
+
${commentMatch} AS comment_match,
|
|
813
|
+
${documentMatch} AS document_match,
|
|
814
|
+
${fuzzyTokenTitleMatch} AS fuzzy_title,
|
|
815
|
+
${fuzzyIdentifierMatch} AS fuzzy_ident,
|
|
816
|
+
${coverageSql} AS token_coverage
|
|
817
|
+
FROM issues
|
|
818
|
+
WHERE issues.company_id = ${companyId}
|
|
819
|
+
AND ${visibleIssueCondition()}
|
|
820
|
+
${matchedWhere}
|
|
821
|
+
)
|
|
822
|
+
${sql.join(branches, sql ` UNION ALL `)}
|
|
823
|
+
`);
|
|
824
|
+
const aggregates = {
|
|
825
|
+
typeCounts: { issue: 0, comment: 0, document: 0 },
|
|
826
|
+
filterOptionCounts: emptyFilterOptionCounts(),
|
|
827
|
+
totals: { current: 0, unfiltered: 0, omit: {} },
|
|
828
|
+
};
|
|
829
|
+
const issueRowsRaw = [];
|
|
830
|
+
for (const row of resultRows) {
|
|
831
|
+
if (row.kind === "result") {
|
|
832
|
+
issueRowsRaw.push({
|
|
833
|
+
id: row.id,
|
|
834
|
+
identifier: row.identifier,
|
|
835
|
+
title: row.title,
|
|
836
|
+
description: row.description,
|
|
837
|
+
status: row.status,
|
|
838
|
+
priority: row.priority,
|
|
839
|
+
assigneeAgentId: row.assigneeAgentId,
|
|
840
|
+
assigneeUserId: row.assigneeUserId,
|
|
841
|
+
projectId: row.projectId,
|
|
842
|
+
createdAt: row.createdAt,
|
|
843
|
+
updatedAt: row.updatedAt,
|
|
844
|
+
score: row.score,
|
|
845
|
+
matchedFields: row.matchedFields,
|
|
846
|
+
commentSnippet: null,
|
|
847
|
+
commentId: null,
|
|
848
|
+
documentSnippet: null,
|
|
849
|
+
documentTitle: null,
|
|
850
|
+
documentKey: null,
|
|
851
|
+
});
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
const count = Number(row.count ?? 0);
|
|
855
|
+
if (row.kind === "type:issue")
|
|
856
|
+
aggregates.typeCounts.issue = count;
|
|
857
|
+
else if (row.kind === "type:comment")
|
|
858
|
+
aggregates.typeCounts.comment = count;
|
|
859
|
+
else if (row.kind === "type:document")
|
|
860
|
+
aggregates.typeCounts.document = count;
|
|
861
|
+
else if (row.kind === "facet:status" && row.value && ISSUE_STATUSES.includes(row.value)) {
|
|
862
|
+
aggregates.filterOptionCounts.status[row.value] = count;
|
|
863
|
+
}
|
|
864
|
+
else if (row.kind === "facet:priority" && row.value && ISSUE_PRIORITIES.includes(row.value)) {
|
|
865
|
+
aggregates.filterOptionCounts.priority[row.value] = count;
|
|
866
|
+
}
|
|
867
|
+
else if (row.kind === "facet:assigneeAgentId" && row.value)
|
|
868
|
+
aggregates.filterOptionCounts.assigneeAgentId[row.value] = count;
|
|
869
|
+
else if (row.kind === "facet:assigneeUserId" && row.value)
|
|
870
|
+
aggregates.filterOptionCounts.assigneeUserId[row.value] = count;
|
|
871
|
+
else if (row.kind === "facet:projectId" && row.value)
|
|
872
|
+
aggregates.filterOptionCounts.projectId[row.value] = count;
|
|
873
|
+
else if (row.kind === "facet:labelId" && row.value)
|
|
874
|
+
aggregates.filterOptionCounts.labelId[row.value] = count;
|
|
875
|
+
else if (row.kind === "facet:updatedWithin" && row.value) {
|
|
876
|
+
aggregates.filterOptionCounts.updatedWithin[row.value] = count;
|
|
877
|
+
}
|
|
878
|
+
else if (row.kind === "total:current")
|
|
879
|
+
aggregates.totals.current = count;
|
|
880
|
+
else if (row.kind === "total:unfiltered")
|
|
881
|
+
aggregates.totals.unfiltered = count;
|
|
882
|
+
else if (row.kind.startsWith("total:omit:")) {
|
|
883
|
+
aggregates.totals.omit[row.kind.slice("total:omit:".length)] = count;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return { rows: await enrichIssueSnippets(issueRowsRaw), aggregates };
|
|
887
|
+
}
|
|
888
|
+
// Fetch best-matching comment/document snippets only for the fetched
|
|
889
|
+
// page window (<= fetchLimit rows) instead of for every matching row.
|
|
890
|
+
async function enrichIssueSnippets(rows) {
|
|
891
|
+
if (!hasSearchText || rows.length === 0)
|
|
892
|
+
return rows;
|
|
893
|
+
const snippetIds = rows
|
|
894
|
+
.filter((row) => {
|
|
895
|
+
const fields = row.matchedFields ?? [];
|
|
896
|
+
return fields.includes("comment") || fields.includes("document");
|
|
897
|
+
})
|
|
898
|
+
.map((row) => row.id);
|
|
899
|
+
if (snippetIds.length === 0)
|
|
900
|
+
return rows;
|
|
901
|
+
const snippetRows = await db.execute(sql `
|
|
902
|
+
SELECT
|
|
903
|
+
target.id AS "issueId",
|
|
904
|
+
best_comment.id AS "commentId",
|
|
905
|
+
best_comment.body AS "commentSnippet",
|
|
906
|
+
best_document.latest_body AS "documentSnippet",
|
|
907
|
+
best_document.title AS "documentTitle",
|
|
908
|
+
best_document.key AS "documentKey"
|
|
909
|
+
FROM unnest(${sqlUuidArray(snippetIds)}) AS target(id)
|
|
910
|
+
LEFT JOIN LATERAL (
|
|
911
|
+
SELECT search_comments.id, search_comments.body
|
|
912
|
+
FROM issue_comments search_comments
|
|
913
|
+
WHERE search_comments.company_id = ${companyId}
|
|
914
|
+
AND search_comments.issue_id = target.id
|
|
915
|
+
AND search_comments.deleted_at IS NULL
|
|
916
|
+
AND (
|
|
917
|
+
search_comments.body ILIKE ${containsPattern}
|
|
918
|
+
OR search_comments.body ILIKE ANY(${tokenPatternArray})
|
|
552
919
|
)
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
920
|
+
ORDER BY
|
|
921
|
+
CASE WHEN search_comments.body ILIKE ${containsPattern} THEN 0 ELSE 1 END,
|
|
922
|
+
search_comments.updated_at DESC,
|
|
923
|
+
search_comments.id DESC
|
|
924
|
+
LIMIT 1
|
|
925
|
+
) best_comment ON true
|
|
926
|
+
LEFT JOIN LATERAL (
|
|
927
|
+
SELECT search_issue_documents.key, search_documents.latest_body, search_documents.title
|
|
928
|
+
FROM issue_documents search_issue_documents
|
|
929
|
+
INNER JOIN documents search_documents
|
|
930
|
+
ON search_documents.id = search_issue_documents.document_id
|
|
931
|
+
AND search_documents.company_id = search_issue_documents.company_id
|
|
932
|
+
WHERE search_issue_documents.company_id = ${companyId}
|
|
933
|
+
AND search_issue_documents.issue_id = target.id
|
|
934
|
+
AND (
|
|
935
|
+
coalesce(search_documents.title, '') ILIKE ${containsPattern}
|
|
936
|
+
OR search_documents.latest_body ILIKE ${containsPattern}
|
|
937
|
+
OR coalesce(search_documents.title, '') ILIKE ANY(${tokenPatternArray})
|
|
938
|
+
OR search_documents.latest_body ILIKE ANY(${tokenPatternArray})
|
|
571
939
|
)
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
940
|
+
ORDER BY
|
|
941
|
+
CASE
|
|
942
|
+
WHEN coalesce(search_documents.title, '') ILIKE ${containsPattern} THEN 0
|
|
943
|
+
WHEN search_documents.latest_body ILIKE ${containsPattern} THEN 1
|
|
944
|
+
ELSE 2
|
|
945
|
+
END,
|
|
946
|
+
search_documents.updated_at DESC,
|
|
947
|
+
search_documents.id DESC
|
|
948
|
+
LIMIT 1
|
|
949
|
+
) best_document ON true
|
|
950
|
+
`);
|
|
951
|
+
const byIssueId = new Map(snippetRows.map((row) => [row.issueId, row]));
|
|
952
|
+
return rows.map((row) => {
|
|
953
|
+
const snippet = byIssueId.get(row.id);
|
|
954
|
+
if (!snippet)
|
|
955
|
+
return row;
|
|
956
|
+
return {
|
|
957
|
+
...row,
|
|
958
|
+
commentSnippet: snippet.commentSnippet,
|
|
959
|
+
commentId: snippet.commentId,
|
|
960
|
+
documentSnippet: snippet.documentSnippet,
|
|
961
|
+
documentTitle: snippet.documentTitle,
|
|
962
|
+
documentKey: snippet.documentKey,
|
|
963
|
+
};
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
// --- agents / projects / artifacts ------------------------------------
|
|
579
967
|
const simpleCondition = simpleTextCondition([
|
|
580
968
|
sql `${agents.name}`,
|
|
581
969
|
sql `${agents.role}`,
|
|
582
970
|
sql `${agents.title}`,
|
|
583
971
|
sql `${agents.capabilities}`,
|
|
584
|
-
], containsPattern,
|
|
585
|
-
const
|
|
586
|
-
|
|
972
|
+
], containsPattern, tokenPatternArray);
|
|
973
|
+
const projectCondition = simpleTextCondition([
|
|
974
|
+
sql `${projects.name}`,
|
|
975
|
+
sql `${projects.description}`,
|
|
976
|
+
], containsPattern, tokenPatternArray);
|
|
977
|
+
async function fetchAgentRows() {
|
|
978
|
+
if (!hasSearchText || !scopeIncludesAgents(scope) || hasIssueOnlyFilters)
|
|
979
|
+
return [];
|
|
980
|
+
return db
|
|
587
981
|
.select({
|
|
588
982
|
id: agents.id,
|
|
589
983
|
title: agents.name,
|
|
590
984
|
description: agents.capabilities,
|
|
591
985
|
role: agents.role,
|
|
986
|
+
createdAt: agents.createdAt,
|
|
592
987
|
updatedAt: agents.updatedAt,
|
|
593
988
|
})
|
|
594
989
|
.from(agents)
|
|
595
990
|
.where(and(eq(agents.companyId, companyId), simpleCondition))
|
|
596
991
|
.orderBy(desc(agents.updatedAt), desc(agents.id))
|
|
597
|
-
.limit(fetchLimit)
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const projectRows = scopeIncludesProjects(scope)
|
|
604
|
-
? await db
|
|
992
|
+
.limit(fetchLimit);
|
|
993
|
+
}
|
|
994
|
+
async function fetchProjectRows() {
|
|
995
|
+
if (!hasSearchText || !scopeIncludesProjects(scope) || hasIssueOnlyFilters)
|
|
996
|
+
return [];
|
|
997
|
+
return db
|
|
605
998
|
.select({
|
|
606
999
|
id: projects.id,
|
|
607
1000
|
title: projects.name,
|
|
608
1001
|
description: projects.description,
|
|
1002
|
+
createdAt: projects.createdAt,
|
|
609
1003
|
updatedAt: projects.updatedAt,
|
|
610
1004
|
})
|
|
611
1005
|
.from(projects)
|
|
612
1006
|
.where(and(eq(projects.companyId, companyId), isNull(projects.archivedAt), projectCondition))
|
|
613
1007
|
.orderBy(desc(projects.updatedAt), desc(projects.id))
|
|
614
|
-
.limit(fetchLimit)
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
1008
|
+
.limit(fetchLimit);
|
|
1009
|
+
}
|
|
1010
|
+
async function countArtifacts(filters = query) {
|
|
1011
|
+
if (!hasSearchText)
|
|
1012
|
+
return 0;
|
|
1013
|
+
const artifactIssueFilters = issueFilterConditions(companyId, filters);
|
|
1014
|
+
const artifactIssueConditions = [
|
|
1015
|
+
eq(issues.companyId, companyId),
|
|
1016
|
+
visibleIssueCondition(),
|
|
1017
|
+
...artifactIssueFilters,
|
|
1018
|
+
];
|
|
1019
|
+
const documentArtifactConditions = [
|
|
1020
|
+
eq(issueDocuments.companyId, companyId),
|
|
1021
|
+
eq(documents.companyId, companyId),
|
|
1022
|
+
or(isNotNull(documents.createdByAgentId), isNotNull(documents.updatedByAgentId)),
|
|
1023
|
+
notInArray(issueDocuments.key, [...SYSTEM_ISSUE_DOCUMENT_KEYS]),
|
|
1024
|
+
sql `(
|
|
1025
|
+
coalesce(${documents.title}, '') ILIKE ${containsPattern} ESCAPE '\\'
|
|
1026
|
+
OR ${documents.latestBody} ILIKE ${containsPattern} ESCAPE '\\'
|
|
1027
|
+
OR coalesce(${issues.identifier}, '') ILIKE ${containsPattern} ESCAPE '\\'
|
|
1028
|
+
OR ${issues.title} ILIKE ${containsPattern} ESCAPE '\\'
|
|
1029
|
+
)`,
|
|
1030
|
+
...artifactIssueConditions,
|
|
1031
|
+
];
|
|
1032
|
+
const workProductConditions = [
|
|
1033
|
+
eq(issueWorkProducts.companyId, companyId),
|
|
1034
|
+
eq(issueWorkProducts.type, "artifact"),
|
|
1035
|
+
eq(issueWorkProducts.provider, "paperclip"),
|
|
1036
|
+
sql `(
|
|
1037
|
+
${issueWorkProducts.title} ILIKE ${containsPattern} ESCAPE '\\'
|
|
1038
|
+
OR coalesce(${issueWorkProducts.summary}, '') ILIKE ${containsPattern} ESCAPE '\\'
|
|
1039
|
+
OR coalesce(${issues.identifier}, '') ILIKE ${containsPattern} ESCAPE '\\'
|
|
1040
|
+
OR ${issues.title} ILIKE ${containsPattern} ESCAPE '\\'
|
|
1041
|
+
)`,
|
|
1042
|
+
...artifactIssueConditions,
|
|
1043
|
+
];
|
|
1044
|
+
const attachmentConditions = [
|
|
1045
|
+
eq(issueAttachments.companyId, companyId),
|
|
1046
|
+
isNull(issueAttachments.issueCommentId),
|
|
1047
|
+
isNotNull(assets.createdByAgentId),
|
|
1048
|
+
sql `(
|
|
1049
|
+
coalesce(${assets.originalFilename}, '') ILIKE ${containsPattern} ESCAPE '\\'
|
|
1050
|
+
OR coalesce(${issues.identifier}, '') ILIKE ${containsPattern} ESCAPE '\\'
|
|
1051
|
+
OR ${issues.title} ILIKE ${containsPattern} ESCAPE '\\'
|
|
1052
|
+
)`,
|
|
1053
|
+
...artifactIssueConditions,
|
|
1054
|
+
];
|
|
1055
|
+
const [documentRows, workProductRows, attachmentRows] = await Promise.all([
|
|
1056
|
+
db
|
|
1057
|
+
.select({ count: sql `count(*)::int` })
|
|
1058
|
+
.from(issueDocuments)
|
|
1059
|
+
.innerJoin(documents, and(eq(issueDocuments.documentId, documents.id), eq(documents.companyId, issueDocuments.companyId)))
|
|
1060
|
+
.innerJoin(issues, and(eq(issueDocuments.issueId, issues.id), eq(issues.companyId, issueDocuments.companyId)))
|
|
1061
|
+
.where(and(...documentArtifactConditions)),
|
|
1062
|
+
db
|
|
1063
|
+
.select({ count: sql `count(*)::int` })
|
|
1064
|
+
.from(issueWorkProducts)
|
|
1065
|
+
.innerJoin(issues, and(eq(issueWorkProducts.issueId, issues.id), eq(issues.companyId, issueWorkProducts.companyId)))
|
|
1066
|
+
.where(and(...workProductConditions)),
|
|
1067
|
+
db
|
|
1068
|
+
.select({ count: sql `count(*)::int` })
|
|
1069
|
+
.from(issueAttachments)
|
|
1070
|
+
.innerJoin(assets, and(eq(issueAttachments.assetId, assets.id), eq(assets.companyId, issueAttachments.companyId)))
|
|
1071
|
+
.innerJoin(issues, and(eq(issueAttachments.issueId, issues.id), eq(issues.companyId, issueAttachments.companyId)))
|
|
1072
|
+
.where(and(...attachmentConditions)),
|
|
1073
|
+
]);
|
|
1074
|
+
return Number(documentRows[0]?.count ?? 0)
|
|
1075
|
+
+ Number(workProductRows[0]?.count ?? 0)
|
|
1076
|
+
+ Number(attachmentRows[0]?.count ?? 0);
|
|
1077
|
+
}
|
|
1078
|
+
async function countAgents(filters = query) {
|
|
1079
|
+
if (!hasSearchText || issueOnlyFiltersActive(filters))
|
|
1080
|
+
return 0;
|
|
1081
|
+
const rows = await db
|
|
1082
|
+
.select({ count: sql `count(*)::int` })
|
|
1083
|
+
.from(agents)
|
|
1084
|
+
.where(and(eq(agents.companyId, companyId), simpleCondition));
|
|
1085
|
+
return Number(rows[0]?.count ?? 0);
|
|
1086
|
+
}
|
|
1087
|
+
async function countProjects(filters = query) {
|
|
1088
|
+
if (!hasSearchText || issueOnlyFiltersActive(filters))
|
|
1089
|
+
return 0;
|
|
1090
|
+
const rows = await db
|
|
1091
|
+
.select({ count: sql `count(*)::int` })
|
|
1092
|
+
.from(projects)
|
|
1093
|
+
.where(and(eq(projects.companyId, companyId), isNull(projects.archivedAt), projectCondition));
|
|
1094
|
+
return Number(rows[0]?.count ?? 0);
|
|
1095
|
+
}
|
|
1096
|
+
async function fetchArtifactRows() {
|
|
1097
|
+
if (!hasSearchText || !scopeIncludesArtifacts(scope))
|
|
1098
|
+
return [];
|
|
1099
|
+
const result = await companyArtifactsService(db).list(companyId, {
|
|
618
1100
|
q: normalizedQuery.slice(0, COMPANY_ARTIFACTS_MAX_QUERY_LENGTH),
|
|
619
1101
|
limit: Math.min(fetchLimit, COMPANY_ARTIFACTS_MAX_LIMIT),
|
|
620
|
-
}
|
|
621
|
-
|
|
1102
|
+
}, { issueConditions: issueFilters });
|
|
1103
|
+
return result.artifacts;
|
|
1104
|
+
}
|
|
1105
|
+
const [company, issueSearchData, artifactRows, agentRows, projectRows, artifactCount, agentCount, projectCount] = await Promise.all([
|
|
1106
|
+
db
|
|
1107
|
+
.select({ issuePrefix: companies.issuePrefix })
|
|
1108
|
+
.from(companies)
|
|
1109
|
+
.where(eq(companies.id, companyId))
|
|
1110
|
+
.then((rows) => rows[0] ?? null),
|
|
1111
|
+
fetchIssueSearchData(),
|
|
1112
|
+
fetchArtifactRows(),
|
|
1113
|
+
fetchAgentRows(),
|
|
1114
|
+
fetchProjectRows(),
|
|
1115
|
+
scopeIncludesArtifacts(scope) ? countArtifacts(query) : Promise.resolve(0),
|
|
1116
|
+
scopeIncludesAgents(scope) ? countAgents(query) : Promise.resolve(0),
|
|
1117
|
+
scopeIncludesProjects(scope) ? countProjects(query) : Promise.resolve(0),
|
|
1118
|
+
]);
|
|
1119
|
+
const prefix = routePrefix(company?.issuePrefix);
|
|
1120
|
+
const { rows: issueRows, aggregates } = issueSearchData;
|
|
1121
|
+
const countsByType = emptySearchCounts();
|
|
1122
|
+
countsByType.issue = aggregates.typeCounts.issue;
|
|
1123
|
+
countsByType.comment = aggregates.typeCounts.comment;
|
|
1124
|
+
countsByType.document = aggregates.typeCounts.document;
|
|
1125
|
+
countsByType.artifact = artifactCount;
|
|
1126
|
+
countsByType.agent = agentCount;
|
|
1127
|
+
countsByType.project = projectCount;
|
|
1128
|
+
const currentTotalCount = (scopeIncludesIssues(scope) ? aggregates.totals.current : 0)
|
|
1129
|
+
+ artifactCount
|
|
1130
|
+
+ agentCount
|
|
1131
|
+
+ projectCount;
|
|
622
1132
|
const results = [
|
|
623
|
-
...issueRows.map((row) =>
|
|
624
|
-
|
|
1133
|
+
...issueRows.map((row) => {
|
|
1134
|
+
const result = issueResult(row, prefix, normalizedQuery, tokens);
|
|
1135
|
+
return {
|
|
1136
|
+
...result,
|
|
1137
|
+
sortCreatedAt: iso(row.createdAt),
|
|
1138
|
+
sortPriorityRank: priorityRank(row.priority),
|
|
1139
|
+
};
|
|
1140
|
+
}),
|
|
1141
|
+
...artifactRows.map((artifact) => ({
|
|
1142
|
+
...artifactResult(artifact, normalizedQuery, tokens),
|
|
1143
|
+
sortCreatedAt: artifact.updatedAt,
|
|
1144
|
+
sortPriorityRank: ISSUE_PRIORITIES.length,
|
|
1145
|
+
})),
|
|
625
1146
|
...agentRows.map((row) => {
|
|
626
1147
|
const terms = matchTerms(normalizedQuery, tokens);
|
|
627
1148
|
const snippet = createSnippet("capabilities", "Agent", row.description ?? row.role ?? row.title, terms);
|
|
@@ -637,6 +1158,8 @@ export function companySearchService(db) {
|
|
|
637
1158
|
snippets: snippet ? [snippet] : [],
|
|
638
1159
|
updatedAt: iso(row.updatedAt),
|
|
639
1160
|
previewImageUrl: null,
|
|
1161
|
+
sortCreatedAt: iso(row.createdAt),
|
|
1162
|
+
sortPriorityRank: ISSUE_PRIORITIES.length,
|
|
640
1163
|
};
|
|
641
1164
|
}),
|
|
642
1165
|
...projectRows.map((row) => {
|
|
@@ -654,22 +1177,48 @@ export function companySearchService(db) {
|
|
|
654
1177
|
snippets: snippet ? [snippet] : [],
|
|
655
1178
|
updatedAt: iso(row.updatedAt),
|
|
656
1179
|
previewImageUrl: null,
|
|
1180
|
+
sortCreatedAt: iso(row.createdAt),
|
|
1181
|
+
sortPriorityRank: ISSUE_PRIORITIES.length,
|
|
657
1182
|
};
|
|
658
1183
|
}),
|
|
659
|
-
].sort((
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1184
|
+
].sort(compareSearchResults(sort));
|
|
1185
|
+
async function countTotalNonIssue(filters) {
|
|
1186
|
+
const [artifactTotal, agentTotal, projectTotal] = await Promise.all([
|
|
1187
|
+
scopeIncludesArtifacts(scope) ? countArtifacts(filters) : Promise.resolve(0),
|
|
1188
|
+
scopeIncludesAgents(scope) ? countAgents(filters) : Promise.resolve(0),
|
|
1189
|
+
scopeIncludesProjects(scope) ? countProjects(filters) : Promise.resolve(0),
|
|
1190
|
+
]);
|
|
1191
|
+
return artifactTotal + agentTotal + projectTotal;
|
|
1192
|
+
}
|
|
1193
|
+
const filtersActive = activeIssueFilters(query);
|
|
1194
|
+
const zeroResults = currentTotalCount === 0 && filtersActive.length > 0
|
|
1195
|
+
? {
|
|
1196
|
+
unfilteredTotal: (scopeIncludesIssues(scope) ? aggregates.totals.unfiltered : 0)
|
|
1197
|
+
+ await countTotalNonIssue(queryWithoutIssueFilters(query)),
|
|
1198
|
+
loosenSuggestions: (await Promise.all(filtersActive.map(async (filter) => {
|
|
1199
|
+
const resultCount = (scopeIncludesIssues(scope) ? aggregates.totals.omit[filter.key] ?? 0 : 0)
|
|
1200
|
+
+ await countTotalNonIssue(queryWithoutFilter(query, filter.key));
|
|
1201
|
+
return {
|
|
1202
|
+
filter: filter.key,
|
|
1203
|
+
values: filter.values,
|
|
1204
|
+
resultCount,
|
|
1205
|
+
additionalCount: Math.max(0, resultCount - currentTotalCount),
|
|
1206
|
+
};
|
|
1207
|
+
}))).sort((left, right) => right.additionalCount - left.additionalCount),
|
|
1208
|
+
}
|
|
1209
|
+
: null;
|
|
1210
|
+
const paged = results.slice(offset, offset + limit).map(stripInternalSortFields);
|
|
665
1211
|
return {
|
|
666
1212
|
query: query.q,
|
|
667
1213
|
normalizedQuery,
|
|
668
1214
|
scope,
|
|
1215
|
+
sort,
|
|
669
1216
|
limit,
|
|
670
1217
|
offset,
|
|
671
1218
|
results: paged,
|
|
672
|
-
countsByType
|
|
1219
|
+
countsByType,
|
|
1220
|
+
filterOptionCounts: aggregates.filterOptionCounts,
|
|
1221
|
+
zeroResults,
|
|
673
1222
|
hasMore: results.length > offset + limit,
|
|
674
1223
|
};
|
|
675
1224
|
},
|