@oneuptime/common 10.4.17 → 10.5.1
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/Models/AnalyticsModels/ExceptionInstance.ts +24 -0
- package/Models/AnalyticsModels/Log.ts +16 -0
- package/Models/AnalyticsModels/Metric.ts +31 -0
- package/Models/AnalyticsModels/MonitorLog.ts +5 -0
- package/Models/AnalyticsModels/Profile.ts +25 -0
- package/Models/AnalyticsModels/ProfileSample.ts +20 -0
- package/Models/AnalyticsModels/Span.ts +23 -0
- package/Models/DatabaseModels/AlertEpisodeMember.ts +2 -0
- package/Models/DatabaseModels/AlertGroupingRule.ts +0 -38
- package/Models/DatabaseModels/AlertLabelRule.ts +152 -0
- package/Models/DatabaseModels/AlertOwnerRule.ts +114 -0
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +7 -0
- package/Models/DatabaseModels/IncidentEpisodeMember.ts +2 -0
- package/Models/DatabaseModels/IncidentGroupingRule.ts +0 -38
- package/Models/DatabaseModels/IncidentLabelRule.ts +114 -0
- package/Models/DatabaseModels/IncidentMember.ts +2 -0
- package/Models/DatabaseModels/IncidentOwnerRule.ts +114 -0
- package/Models/DatabaseModels/IncidentSla.ts +2 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +224 -0
- package/Models/DatabaseModels/Index.ts +2 -2
- package/Models/DatabaseModels/MetricPipelineRule.ts +2 -0
- package/Models/DatabaseModels/MonitorProbe.ts +2 -0
- package/Models/DatabaseModels/MonitorTest.ts +2 -0
- package/Models/DatabaseModels/OnCallDutyPolicyEscalationRule.ts +2 -0
- package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.ts +2 -0
- package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.ts +2 -0
- package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.ts +2 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +2 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.ts +2 -0
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +2 -0
- package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -0
- package/Models/DatabaseModels/ProjectOidc.ts +4 -0
- package/Models/DatabaseModels/ProjectSCIM.ts +4 -0
- package/Models/DatabaseModels/ProjectSso.ts +4 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +220 -0
- package/Models/DatabaseModels/ScheduledMaintenanceLabelRule.ts +152 -0
- package/Models/DatabaseModels/ScheduledMaintenanceOwnerRule.ts +152 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +224 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +6 -0
- package/Models/DatabaseModels/StatusPageSCIM.ts +4 -0
- package/Models/DatabaseModels/StatusPageSCIMLog.ts +2 -0
- package/Models/DatabaseModels/StatusPageSso.ts +6 -0
- package/Models/DatabaseModels/Team.ts +41 -0
- package/Models/DatabaseModels/TeamComplianceSetting.ts +4 -0
- package/Models/DatabaseModels/{ServiceMonitor.ts → TeamCustomField.ts} +95 -200
- package/Models/DatabaseModels/TelemetryException.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
- package/Models/DatabaseModels/WorkflowLog.ts +2 -0
- package/Models/DatabaseModels/WorkflowVariable.ts +2 -0
- package/Server/EnvironmentConfig.ts +3 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779653508434-AddLabelInheritanceAndScheduledMaintenanceResources.ts +160 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779708719656-AddAffectedResourcesToTemplates.ts +197 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779739410559-MigrationName.ts +36 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779742211961-AttachServiceToScheduledMaintenanceTemplatesAndLabelRules.ts +128 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779790539196-MigrationName.ts +53 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779823516881-ExpandOwnerRuleInheritFlags.ts +73 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779827700000-RenameStatusPageZhToZhCN.ts +62 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
- package/Server/Middleware/TelemetryIngestionDisabled.ts +32 -0
- package/Server/Services/AlertGroupingEngineService.ts +0 -29
- package/Server/Services/AlertLabelRuleEngineService.ts +129 -0
- package/Server/Services/AlertOwnerRuleEngineService.ts +205 -1
- package/Server/Services/IncidentGroupingEngineService.ts +0 -37
- package/Server/Services/IncidentLabelRuleEngineService.ts +83 -0
- package/Server/Services/IncidentOwnerRuleEngineService.ts +208 -10
- package/Server/Services/IncidentService.ts +139 -1
- package/Server/Services/Index.ts +0 -2
- package/Server/Services/MonitorProbeService.ts +56 -0
- package/Server/Services/MonitorService.ts +55 -0
- package/Server/Services/ProjectService.ts +17 -8
- package/Server/Services/ScheduledMaintenanceLabelRuleEngineService.ts +129 -0
- package/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.ts +289 -7
- package/Server/Services/StatusPageService.ts +30 -0
- package/Server/Services/TeamCustomFieldService.ts +9 -0
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +226 -28
- package/Server/Types/Database/Permissions/EditionPermission.ts +46 -0
- package/Server/Types/Database/Permissions/TablePermission.ts +8 -1
- package/Server/Utils/Monitor/MonitorAlert.ts +35 -0
- package/Server/Utils/Monitor/MonitorIncident.ts +244 -34
- package/Tests/Server/Middleware/UserAuthorization.test.ts +11 -19
- package/Tests/Types/Permission.test.ts +129 -1
- package/Types/Accounts/AccountsLanguage.ts +10 -1
- package/Types/AdminDashboard/AdminDashboardLanguage.ts +10 -1
- package/Types/BaseDatabase/TableEditionAccessControl.ts +3 -0
- package/Types/Dashboard/DashboardLanguage.ts +10 -1
- package/Types/Database/AccessControl/TableEditionAccessControl.ts +8 -0
- package/Types/Date.ts +1 -1
- package/Types/Docs/DocsLanguage.ts +10 -1
- package/Types/Permission.ts +87 -54
- package/Types/StatusPage/StatusPageLanguage.ts +10 -1
- package/UI/Components/Charts/Area/AreaChart.tsx +1 -1
- package/UI/Components/Charts/Bar/BarChart.tsx +1 -1
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +5 -1
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +1 -1
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +11 -1
- package/UI/Components/Charts/Line/LineChart.tsx +1 -1
- package/UI/Components/Charts/Utils/XAxis.ts +21 -48
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +1808 -0
- package/UI/Components/Forms/Fields/FormField.tsx +69 -29
- package/UI/Components/Link/Link.tsx +13 -1
- package/UI/Components/ModelDetail/ModelDetail.tsx +20 -19
- package/UI/Components/ModelTable/BaseModelTable.tsx +5 -0
- package/UI/Utils/User.ts +16 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +39 -2
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +16 -0
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +31 -0
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MonitorLog.js +5 -0
- package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Profile.js +40 -2
- package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ProfileSample.js +35 -2
- package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +23 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertGroupingRule.js +0 -39
- package/build/dist/Models/DatabaseModels/AlertGroupingRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertLabelRule.js +156 -0
- package/build/dist/Models/DatabaseModels/AlertLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertOwnerRule.js +117 -0
- package/build/dist/Models/DatabaseModels/AlertOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js +0 -39
- package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentLabelRule.js +117 -0
- package/build/dist/Models/DatabaseModels/IncidentLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentOwnerRule.js +117 -0
- package/build/dist/Models/DatabaseModels/IncidentOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentSla.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentSla.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +216 -0
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -2
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MetricPipelineRule.js +2 -0
- package/build/dist/Models/DatabaseModels/MetricPipelineRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorProbe.js +2 -0
- package/build/dist/Models/DatabaseModels/MonitorProbe.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTest.js +2 -0
- package/build/dist/Models/DatabaseModels/MonitorTest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRule.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectOidc.js +4 -0
- package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectSCIM.js +4 -0
- package/build/dist/Models/DatabaseModels/ProjectSCIM.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectSso.js +4 -0
- package/build/dist/Models/DatabaseModels/ProjectSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +216 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceLabelRule.js +156 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceOwnerRule.js +156 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +216 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +6 -0
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSCIM.js +4 -0
- package/build/dist/Models/DatabaseModels/StatusPageSCIM.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSCIMLog.js +2 -0
- package/build/dist/Models/DatabaseModels/StatusPageSCIMLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSso.js +6 -0
- package/build/dist/Models/DatabaseModels/StatusPageSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Team.js +42 -0
- package/build/dist/Models/DatabaseModels/Team.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +4 -0
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/{ServiceMonitor.js → TeamCustomField.js} +108 -209
- package/build/dist/Models/DatabaseModels/TeamCustomField.js.map +1 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js +2 -0
- package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +2 -0
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +1 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779653508434-AddLabelInheritanceAndScheduledMaintenanceResources.js +60 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779653508434-AddLabelInheritanceAndScheduledMaintenanceResources.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779708719656-AddAffectedResourcesToTemplates.js +74 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779708719656-AddAffectedResourcesToTemplates.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779739410559-MigrationName.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779739410559-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779742211961-AttachServiceToScheduledMaintenanceTemplatesAndLabelRules.js +50 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779742211961-AttachServiceToScheduledMaintenanceTemplatesAndLabelRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779790539196-MigrationName.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779790539196-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779823516881-ExpandOwnerRuleInheritFlags.js +30 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779823516881-ExpandOwnerRuleInheritFlags.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779827700000-RenameStatusPageZhToZhCN.js +50 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779827700000-RenameStatusPageZhToZhCN.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngestionDisabled.js +22 -0
- package/build/dist/Server/Middleware/TelemetryIngestionDisabled.js.map +1 -0
- package/build/dist/Server/Services/AlertGroupingEngineService.js +0 -25
- package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertLabelRuleEngineService.js +117 -3
- package/build/dist/Server/Services/AlertLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AlertOwnerRuleEngineService.js +175 -5
- package/build/dist/Server/Services/AlertOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentGroupingEngineService.js +0 -31
- package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentLabelRuleEngineService.js +76 -3
- package/build/dist/Server/Services/IncidentLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js +176 -14
- package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +104 -1
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +0 -2
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorProbeService.js +46 -0
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +40 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +17 -8
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js +117 -3
- package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js +245 -10
- package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +24 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamCustomFieldService.js +9 -0
- package/build/dist/Server/Services/TeamCustomFieldService.js.map +1 -0
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +166 -26
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +45 -0
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js +7 -1
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +30 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +200 -31
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js +8 -15
- package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js.map +1 -1
- package/build/dist/Tests/Types/Permission.test.js +90 -1
- package/build/dist/Tests/Types/Permission.test.js.map +1 -1
- package/build/dist/Types/Accounts/AccountsLanguage.js +10 -1
- package/build/dist/Types/Accounts/AccountsLanguage.js.map +1 -1
- package/build/dist/Types/AdminDashboard/AdminDashboardLanguage.js +10 -1
- package/build/dist/Types/AdminDashboard/AdminDashboardLanguage.js.map +1 -1
- package/build/dist/Types/BaseDatabase/TableEditionAccessControl.js +2 -0
- package/build/dist/Types/BaseDatabase/TableEditionAccessControl.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardLanguage.js +10 -1
- package/build/dist/Types/Dashboard/DashboardLanguage.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/TableEditionAccessControl.js +6 -0
- package/build/dist/Types/Database/AccessControl/TableEditionAccessControl.js.map +1 -0
- package/build/dist/Types/Date.js +1 -1
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Docs/DocsLanguage.js +10 -1
- package/build/dist/Types/Docs/DocsLanguage.js.map +1 -1
- package/build/dist/Types/Permission.js +80 -44
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageLanguage.js +10 -1
- package/build/dist/Types/StatusPage/StatusPageLanguage.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +5 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +11 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +21 -47
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +1125 -0
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -0
- package/build/dist/UI/Components/Forms/Fields/FormField.js +28 -10
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Link/Link.js +11 -2
- package/build/dist/UI/Components/Link/Link.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js +20 -18
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +4 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Utils/User.js +13 -0
- package/build/dist/UI/Utils/User.js.map +1 -1
- package/package.json +1 -1
- package/Server/Services/ServiceMonitorService.ts +0 -57
- package/build/dist/Models/DatabaseModels/ServiceMonitor.js.map +0 -1
- package/build/dist/Server/Services/ServiceMonitorService.js +0 -56
- package/build/dist/Server/Services/ServiceMonitorService.js.map +0 -1
|
@@ -0,0 +1,1125 @@
|
|
|
1
|
+
import Label from "../../../Models/DatabaseModels/Label";
|
|
2
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
3
|
+
import Search from "../../../Types/BaseDatabase/Search";
|
|
4
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
5
|
+
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
6
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
7
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
8
|
+
import ModelAPI from "../../Utils/ModelAPI/ModelAPI";
|
|
9
|
+
import Icon from "../Icon/Icon";
|
|
10
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
11
|
+
const SEARCH_DEBOUNCE_MS = 250;
|
|
12
|
+
const SEARCH_PAGE_SIZE = 50;
|
|
13
|
+
const LABEL_PREVIEW_LIMIT = 50;
|
|
14
|
+
const flattenOptions = (options) => {
|
|
15
|
+
if (!options) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const flat = [];
|
|
19
|
+
for (const item of options) {
|
|
20
|
+
if (item &&
|
|
21
|
+
typeof item === "object" &&
|
|
22
|
+
"options" in item &&
|
|
23
|
+
Array.isArray(item.options)) {
|
|
24
|
+
for (const sub of item.options) {
|
|
25
|
+
flat.push(sub);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
flat.push(item);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return flat;
|
|
33
|
+
};
|
|
34
|
+
/*
|
|
35
|
+
* Pull a string key out of any incoming DropdownValue (string | number |
|
|
36
|
+
* boolean | ObjectID-stringified). All comparisons + map keys use the
|
|
37
|
+
* string form to dodge mismatches between numeric and string IDs.
|
|
38
|
+
*/
|
|
39
|
+
const valueKey = (v) => {
|
|
40
|
+
if (typeof v === "string") {
|
|
41
|
+
return v;
|
|
42
|
+
}
|
|
43
|
+
return String(v);
|
|
44
|
+
};
|
|
45
|
+
/*
|
|
46
|
+
* Normalize the prop value down to a list of string keys regardless of
|
|
47
|
+
* the shape callers pass. Mirrors react-select-Dropdown's value coercion
|
|
48
|
+
* so the form layer can keep storing whichever shape it has today.
|
|
49
|
+
*/
|
|
50
|
+
const valueToKeys = (v) => {
|
|
51
|
+
if (v === undefined || v === null) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
if (typeof v === "string") {
|
|
55
|
+
return v === "" ? [] : [v];
|
|
56
|
+
}
|
|
57
|
+
if (typeof v === "number" || typeof v === "boolean") {
|
|
58
|
+
return [String(v)];
|
|
59
|
+
}
|
|
60
|
+
if (v instanceof ObjectID) {
|
|
61
|
+
return [v.toString()];
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(v)) {
|
|
64
|
+
const keys = [];
|
|
65
|
+
for (const item of v) {
|
|
66
|
+
if (item === undefined || item === null) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (item instanceof ObjectID) {
|
|
70
|
+
keys.push(item.toString());
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (typeof item === "string") {
|
|
74
|
+
if (item !== "") {
|
|
75
|
+
keys.push(item);
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (typeof item === "number" || typeof item === "boolean") {
|
|
80
|
+
keys.push(String(item));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (typeof item === "object" && "value" in item) {
|
|
84
|
+
keys.push(valueKey(item.value));
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return keys;
|
|
89
|
+
}
|
|
90
|
+
if (typeof v === "object" && v && "value" in v) {
|
|
91
|
+
return [valueKey(v.value)];
|
|
92
|
+
}
|
|
93
|
+
return [];
|
|
94
|
+
};
|
|
95
|
+
/*
|
|
96
|
+
* Best-effort runtime detection of a `labels` ManyToMany. The convention
|
|
97
|
+
* across OneUptime models is a property literally named `labels` typed
|
|
98
|
+
* `Array<Label>` (44 such models at last count), so a property-existence
|
|
99
|
+
* probe is the cheapest reliable check we can do without yanking column
|
|
100
|
+
* metadata. Callers can override via `enableLabelsTab` if heuristics fail.
|
|
101
|
+
*/
|
|
102
|
+
const detectLabelsField = (ModelType) => {
|
|
103
|
+
var _a;
|
|
104
|
+
if (!ModelType) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const instance = new ModelType();
|
|
109
|
+
/*
|
|
110
|
+
* OneUptime models declare `public labels?: Array<Label> = undefined;`
|
|
111
|
+
* so the property is present on the instance (initialized to undefined)
|
|
112
|
+
* and the `in` check is reliable. We add the column-metadata probe
|
|
113
|
+
* underneath as a belt-and-suspenders fallback in case a future model
|
|
114
|
+
* declares the field differently.
|
|
115
|
+
*/
|
|
116
|
+
if ("labels" in instance) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
const metaProbe = instance;
|
|
120
|
+
if (typeof metaProbe.getTableColumnMetadata === "function" &&
|
|
121
|
+
metaProbe.getTableColumnMetadata("labels")) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (typeof metaProbe.getTableColumns === "function") {
|
|
125
|
+
const cols = metaProbe.getTableColumns();
|
|
126
|
+
if ((_a = cols === null || cols === void 0 ? void 0 : cols.columns) === null || _a === void 0 ? void 0 : _a.includes("labels")) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
catch (_b) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const EntityDropdown = (props) => {
|
|
137
|
+
const isMulti = Boolean(props.isMultiSelect);
|
|
138
|
+
const modelType = props.modelType;
|
|
139
|
+
const labelField = props.labelField || "name";
|
|
140
|
+
const valueField = props.valueField || "_id";
|
|
141
|
+
/*
|
|
142
|
+
* Color column auto-detection mirrors ModelForm.fetchDropdownOptions —
|
|
143
|
+
* BaseModel exposes getFirstColorColumn() which returns the first column
|
|
144
|
+
* decorated as a color (Severity.color, Status.color, etc.). When set,
|
|
145
|
+
* we include it in the server-side SELECT so our supplemental searches
|
|
146
|
+
* carry the same color metadata as the form's pre-fetch.
|
|
147
|
+
*/
|
|
148
|
+
const detectedColorField = useMemo(() => {
|
|
149
|
+
if (!modelType) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const probe = new modelType();
|
|
154
|
+
if (typeof probe.getFirstColorColumn === "function") {
|
|
155
|
+
return probe.getFirstColorColumn() || undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (_a) {
|
|
159
|
+
// Model can't be instantiated for inspection; fall back to no color.
|
|
160
|
+
}
|
|
161
|
+
return undefined;
|
|
162
|
+
}, [modelType]);
|
|
163
|
+
const colorField = props.colorField || detectedColorField;
|
|
164
|
+
const hasLabelsAutoDetected = useMemo(() => {
|
|
165
|
+
return detectLabelsField(modelType);
|
|
166
|
+
}, [modelType]);
|
|
167
|
+
/*
|
|
168
|
+
* The Labels tab is only meaningful on multi-select against a labeled
|
|
169
|
+
* entity. enableLabelsTab is an opt-out override (or explicit opt-in for
|
|
170
|
+
* cases where the heuristic misses).
|
|
171
|
+
*/
|
|
172
|
+
const labelsTabEnabled = isMulti &&
|
|
173
|
+
(props.enableLabelsTab !== undefined
|
|
174
|
+
? props.enableLabelsTab
|
|
175
|
+
: hasLabelsAutoDetected) &&
|
|
176
|
+
Boolean(modelType);
|
|
177
|
+
/*
|
|
178
|
+
* optionsCache is the single source of truth for which DropdownOption goes
|
|
179
|
+
* with which value. It's seeded with the props.options the form pre-fetched
|
|
180
|
+
* and grows as the user types (server-side search) or as we resolve
|
|
181
|
+
* previously-selected values that weren't in the first page.
|
|
182
|
+
*/
|
|
183
|
+
const initialFlat = useMemo(() => {
|
|
184
|
+
return flattenOptions(props.options);
|
|
185
|
+
}, [props.options]);
|
|
186
|
+
const optionsCacheRef = useRef(new Map());
|
|
187
|
+
/*
|
|
188
|
+
* Seed the cache *during render* rather than in a useEffect. A pure-effect
|
|
189
|
+
* seed only fires after first paint, which means the first render's
|
|
190
|
+
* `selectedOptions` lookup misses, the chip falls back to "label = raw
|
|
191
|
+
* UUID", and the user sees an ID flash by before the next render swaps
|
|
192
|
+
* in the real label. Mutating a ref during render is safe because we
|
|
193
|
+
* don't read derived state from it within the same render — we read it
|
|
194
|
+
* via `selectedOptions` below, whose useMemo deps already include the
|
|
195
|
+
* inputs that change the cache.
|
|
196
|
+
*/
|
|
197
|
+
for (const opt of initialFlat) {
|
|
198
|
+
optionsCacheRef.current.set(valueKey(opt.value), opt);
|
|
199
|
+
}
|
|
200
|
+
/*
|
|
201
|
+
* selectedKeys is the source of truth for what the user has picked. We
|
|
202
|
+
* never mirror it from props beyond the initial sync — props.value updates
|
|
203
|
+
* are honored through the effect below so saved-state restores work.
|
|
204
|
+
*/
|
|
205
|
+
const externalKeys = useMemo(() => {
|
|
206
|
+
/*
|
|
207
|
+
* value takes precedence over initialValue (matching react-select
|
|
208
|
+
* controlled-component semantics). We intentionally treat `undefined`
|
|
209
|
+
* as "fall back to initialValue" and any other value as authoritative
|
|
210
|
+
* so callers can clear the field by passing null.
|
|
211
|
+
*/
|
|
212
|
+
const source = props.value !== undefined ? props.value : props.initialValue;
|
|
213
|
+
return valueToKeys(source);
|
|
214
|
+
}, [props.value, props.initialValue]);
|
|
215
|
+
/*
|
|
216
|
+
* Also harvest DropdownOption envelopes from props.value / initialValue,
|
|
217
|
+
* so a parent that hands us a full option (rather than a raw ID) gets
|
|
218
|
+
* its label cached immediately — no resolve round-trip needed.
|
|
219
|
+
*/
|
|
220
|
+
const inboundOptions = useMemo(() => {
|
|
221
|
+
const collected = [];
|
|
222
|
+
const candidates = [
|
|
223
|
+
props.value,
|
|
224
|
+
props.initialValue,
|
|
225
|
+
];
|
|
226
|
+
for (const candidate of candidates) {
|
|
227
|
+
if (!candidate) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (Array.isArray(candidate)) {
|
|
231
|
+
for (const item of candidate) {
|
|
232
|
+
if (item &&
|
|
233
|
+
typeof item === "object" &&
|
|
234
|
+
!(item instanceof ObjectID) &&
|
|
235
|
+
"value" in item &&
|
|
236
|
+
"label" in item) {
|
|
237
|
+
collected.push(item);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (typeof candidate === "object" &&
|
|
243
|
+
!(candidate instanceof ObjectID) &&
|
|
244
|
+
"value" in candidate &&
|
|
245
|
+
"label" in candidate) {
|
|
246
|
+
collected.push(candidate);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return collected;
|
|
250
|
+
}, [props.value, props.initialValue]);
|
|
251
|
+
/*
|
|
252
|
+
* Same reasoning as initialFlat — seed at render time so the first paint
|
|
253
|
+
* has the labels.
|
|
254
|
+
*/
|
|
255
|
+
for (const opt of inboundOptions) {
|
|
256
|
+
optionsCacheRef.current.set(valueKey(opt.value), opt);
|
|
257
|
+
}
|
|
258
|
+
const [selectedKeys, setSelectedKeys] = useState(externalKeys);
|
|
259
|
+
/*
|
|
260
|
+
* Mirror prop value back into local state when the parent rewrites it
|
|
261
|
+
* (e.g. form reset, saved-view restore). We skip the round-trip if the
|
|
262
|
+
* arrays are identical to avoid render thrash.
|
|
263
|
+
*/
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
const same = externalKeys.length === selectedKeys.length &&
|
|
266
|
+
externalKeys.every((k, i) => {
|
|
267
|
+
return k === selectedKeys[i];
|
|
268
|
+
});
|
|
269
|
+
if (same) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
setSelectedKeys(externalKeys);
|
|
273
|
+
// Also stash any new options that came in via props into the cache.
|
|
274
|
+
for (const opt of initialFlat) {
|
|
275
|
+
optionsCacheRef.current.set(valueKey(opt.value), opt);
|
|
276
|
+
}
|
|
277
|
+
}, [externalKeys.join("|")]);
|
|
278
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
279
|
+
const [searchResults, setSearchResults] = useState([]);
|
|
280
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
281
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
282
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
283
|
+
const [activeTab, setActiveTab] = useState("options");
|
|
284
|
+
// Labels tab state.
|
|
285
|
+
const [allLabels, setAllLabels] = useState([]);
|
|
286
|
+
const [isLoadingLabels, setIsLoadingLabels] = useState(false);
|
|
287
|
+
const [labelsLoaded, setLabelsLoaded] = useState(false);
|
|
288
|
+
const [selectedLabelIds, setSelectedLabelIds] = useState([]);
|
|
289
|
+
const [isApplyingLabels, setIsApplyingLabels] = useState(false);
|
|
290
|
+
const [labelError, setLabelError] = useState("");
|
|
291
|
+
const [expandedLabelIds, setExpandedLabelIds] = useState(new Set());
|
|
292
|
+
const [resourcesByLabel, setResourcesByLabel] = useState({});
|
|
293
|
+
const [loadingLabelIds, setLoadingLabelIds] = useState(new Set());
|
|
294
|
+
const [labelLoadErrors, setLabelLoadErrors] = useState({});
|
|
295
|
+
const containerRef = useRef(null);
|
|
296
|
+
const inputRef = useRef(null);
|
|
297
|
+
const debounceRef = useRef(null);
|
|
298
|
+
const searchSeqRef = useRef(0);
|
|
299
|
+
// Click-outside closes the popover.
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
const handle = (event) => {
|
|
302
|
+
if (containerRef.current &&
|
|
303
|
+
event.target instanceof Node &&
|
|
304
|
+
!containerRef.current.contains(event.target)) {
|
|
305
|
+
setIsOpen(false);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
document.addEventListener("mousedown", handle);
|
|
309
|
+
return () => {
|
|
310
|
+
document.removeEventListener("mousedown", handle);
|
|
311
|
+
};
|
|
312
|
+
}, []);
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
return () => {
|
|
315
|
+
if (debounceRef.current !== null) {
|
|
316
|
+
window.clearTimeout(debounceRef.current);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}, []);
|
|
320
|
+
/*
|
|
321
|
+
* BaseModel -> DropdownOption. Pulls label/value (and optional color)
|
|
322
|
+
* via the configured field names. We don't try to be clever about nested
|
|
323
|
+
* objects — the form pre-fetcher follows the same flat convention.
|
|
324
|
+
*/
|
|
325
|
+
const modelToOption = useCallback((item) => {
|
|
326
|
+
var _a;
|
|
327
|
+
const raw = item;
|
|
328
|
+
const valueRaw = (_a = raw[valueField]) !== null && _a !== void 0 ? _a : item._id;
|
|
329
|
+
const labelRaw = raw[labelField];
|
|
330
|
+
if (valueRaw === undefined || valueRaw === null) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
const label = typeof labelRaw === "string"
|
|
334
|
+
? labelRaw
|
|
335
|
+
: labelRaw &&
|
|
336
|
+
typeof labelRaw.toString ===
|
|
337
|
+
"function"
|
|
338
|
+
? labelRaw.toString()
|
|
339
|
+
: "";
|
|
340
|
+
const valueStr = typeof valueRaw === "string"
|
|
341
|
+
? valueRaw
|
|
342
|
+
: valueRaw.toString();
|
|
343
|
+
const option = {
|
|
344
|
+
value: valueStr,
|
|
345
|
+
label: label || valueStr,
|
|
346
|
+
};
|
|
347
|
+
if (colorField) {
|
|
348
|
+
const colorRaw = raw[colorField];
|
|
349
|
+
if (colorRaw) {
|
|
350
|
+
option.color = colorRaw;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return option;
|
|
354
|
+
}, [labelField, valueField, colorField]);
|
|
355
|
+
/*
|
|
356
|
+
* Server-side search. Runs when the popover is open on the options tab.
|
|
357
|
+
* Static dropdowns (no modelType) skip the API and filter the seeded
|
|
358
|
+
* options client-side instead.
|
|
359
|
+
*/
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
if (debounceRef.current !== null) {
|
|
362
|
+
window.clearTimeout(debounceRef.current);
|
|
363
|
+
}
|
|
364
|
+
if (!isOpen || activeTab !== "options" || !modelType) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const trimmed = searchQuery.trim();
|
|
368
|
+
const mySeq = ++searchSeqRef.current;
|
|
369
|
+
setIsLoading(true);
|
|
370
|
+
debounceRef.current = window.setTimeout(async () => {
|
|
371
|
+
try {
|
|
372
|
+
const query = {};
|
|
373
|
+
if (trimmed.length > 0) {
|
|
374
|
+
query[labelField] = new Search(trimmed);
|
|
375
|
+
}
|
|
376
|
+
const baseSelect = {
|
|
377
|
+
_id: true,
|
|
378
|
+
[labelField]: true,
|
|
379
|
+
};
|
|
380
|
+
if (colorField) {
|
|
381
|
+
baseSelect[colorField] = true;
|
|
382
|
+
}
|
|
383
|
+
const result = await ModelAPI.getList({
|
|
384
|
+
modelType: modelType,
|
|
385
|
+
query: query,
|
|
386
|
+
limit: SEARCH_PAGE_SIZE,
|
|
387
|
+
skip: 0,
|
|
388
|
+
select: baseSelect,
|
|
389
|
+
sort: { [labelField]: SortOrder.Ascending },
|
|
390
|
+
});
|
|
391
|
+
if (mySeq !== searchSeqRef.current) {
|
|
392
|
+
return; // stale
|
|
393
|
+
}
|
|
394
|
+
const options = [];
|
|
395
|
+
for (const item of result.data) {
|
|
396
|
+
const opt = modelToOption(item);
|
|
397
|
+
if (!opt) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
options.push(opt);
|
|
401
|
+
optionsCacheRef.current.set(valueKey(opt.value), opt);
|
|
402
|
+
}
|
|
403
|
+
setSearchResults(options);
|
|
404
|
+
}
|
|
405
|
+
catch (_a) {
|
|
406
|
+
if (mySeq === searchSeqRef.current) {
|
|
407
|
+
setSearchResults([]);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
finally {
|
|
411
|
+
if (mySeq === searchSeqRef.current) {
|
|
412
|
+
setIsLoading(false);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}, trimmed === "" ? 0 : SEARCH_DEBOUNCE_MS);
|
|
416
|
+
}, [
|
|
417
|
+
searchQuery,
|
|
418
|
+
isOpen,
|
|
419
|
+
activeTab,
|
|
420
|
+
modelType,
|
|
421
|
+
labelField,
|
|
422
|
+
colorField,
|
|
423
|
+
modelToOption,
|
|
424
|
+
]);
|
|
425
|
+
/*
|
|
426
|
+
* Resolve previously-selected values whose labels we don't yet have. Fires
|
|
427
|
+
* once on mount and whenever externalKeys grows beyond what's in cache —
|
|
428
|
+
* keeps the chips readable even when the form was saved with selections
|
|
429
|
+
* that aren't on the dropdown's first page.
|
|
430
|
+
*/
|
|
431
|
+
useEffect(() => {
|
|
432
|
+
if (!modelType) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const missing = selectedKeys.filter((key) => {
|
|
436
|
+
return !optionsCacheRef.current.has(key);
|
|
437
|
+
});
|
|
438
|
+
if (missing.length === 0) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
let cancelled = false;
|
|
442
|
+
const resolve = async () => {
|
|
443
|
+
try {
|
|
444
|
+
const baseSelect = {
|
|
445
|
+
_id: true,
|
|
446
|
+
[labelField]: true,
|
|
447
|
+
};
|
|
448
|
+
if (colorField) {
|
|
449
|
+
baseSelect[colorField] = true;
|
|
450
|
+
}
|
|
451
|
+
const result = await ModelAPI.getList({
|
|
452
|
+
modelType: modelType,
|
|
453
|
+
query: {
|
|
454
|
+
_id: new Includes(missing),
|
|
455
|
+
},
|
|
456
|
+
limit: missing.length,
|
|
457
|
+
skip: 0,
|
|
458
|
+
select: baseSelect,
|
|
459
|
+
sort: {},
|
|
460
|
+
});
|
|
461
|
+
if (cancelled) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
for (const item of result.data) {
|
|
465
|
+
const opt = modelToOption(item);
|
|
466
|
+
if (opt) {
|
|
467
|
+
optionsCacheRef.current.set(valueKey(opt.value), opt);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// Force a re-render so chips pick up the resolved labels.
|
|
471
|
+
setSelectedKeys((prev) => {
|
|
472
|
+
return [...prev];
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
catch (_a) {
|
|
476
|
+
// Leave the unresolved chips as raw IDs.
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
void resolve();
|
|
480
|
+
return () => {
|
|
481
|
+
cancelled = true;
|
|
482
|
+
};
|
|
483
|
+
}, [
|
|
484
|
+
selectedKeys.join("|"),
|
|
485
|
+
modelType,
|
|
486
|
+
labelField,
|
|
487
|
+
colorField,
|
|
488
|
+
modelToOption,
|
|
489
|
+
]);
|
|
490
|
+
/*
|
|
491
|
+
* Lazy-load the project's labels the first time the user clicks the
|
|
492
|
+
* Labels tab — same gating as AffectedResourcesPicker. DON'T add
|
|
493
|
+
* isLoadingLabels to the deps; setting it inside the effect would
|
|
494
|
+
* self-cancel the in-flight request.
|
|
495
|
+
*/
|
|
496
|
+
useEffect(() => {
|
|
497
|
+
if (!labelsTabEnabled || activeTab !== "labels" || labelsLoaded) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
let cancelled = false;
|
|
501
|
+
const loadLabels = async () => {
|
|
502
|
+
setIsLoadingLabels(true);
|
|
503
|
+
setLabelError("");
|
|
504
|
+
try {
|
|
505
|
+
const result = await ModelAPI.getList({
|
|
506
|
+
modelType: Label,
|
|
507
|
+
query: {},
|
|
508
|
+
limit: LIMIT_PER_PROJECT,
|
|
509
|
+
skip: 0,
|
|
510
|
+
select: { _id: true, name: true, color: true },
|
|
511
|
+
sort: { name: SortOrder.Ascending },
|
|
512
|
+
});
|
|
513
|
+
if (cancelled) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
setAllLabels(result.data || []);
|
|
517
|
+
setLabelsLoaded(true);
|
|
518
|
+
}
|
|
519
|
+
catch (_a) {
|
|
520
|
+
if (cancelled) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
setLabelError("Failed to load labels. You may not have permission to read labels.");
|
|
524
|
+
}
|
|
525
|
+
finally {
|
|
526
|
+
if (!cancelled) {
|
|
527
|
+
setIsLoadingLabels(false);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
void loadLabels();
|
|
532
|
+
return () => {
|
|
533
|
+
cancelled = true;
|
|
534
|
+
};
|
|
535
|
+
}, [labelsTabEnabled, activeTab, labelsLoaded]);
|
|
536
|
+
const filteredLabels = useMemo(() => {
|
|
537
|
+
const q = searchQuery.trim().toLowerCase();
|
|
538
|
+
if (q === "") {
|
|
539
|
+
return allLabels;
|
|
540
|
+
}
|
|
541
|
+
return allLabels.filter((label) => {
|
|
542
|
+
const name = (label.name || "").toLowerCase();
|
|
543
|
+
return name.includes(q);
|
|
544
|
+
});
|
|
545
|
+
}, [allLabels, searchQuery]);
|
|
546
|
+
/*
|
|
547
|
+
* Available results = initialFlat (the form's pre-fetched set, which has
|
|
548
|
+
* the color/labels metadata we want to preserve) UNIONed with searchResults
|
|
549
|
+
* (the supplemental server-side hits for big lists), filtered by the
|
|
550
|
+
* current query. Even for entity-backed dropdowns we keep initialFlat in
|
|
551
|
+
* the mix so colors and any other rich fields the pre-fetch carried
|
|
552
|
+
* survive — our supplemental fetch only requests the minimum SELECT.
|
|
553
|
+
*/
|
|
554
|
+
const optionsList = useMemo(() => {
|
|
555
|
+
const q = searchQuery.trim().toLowerCase();
|
|
556
|
+
const matches = (opt) => {
|
|
557
|
+
if (q === "") {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
return opt.label.toLowerCase().includes(q);
|
|
561
|
+
};
|
|
562
|
+
const seen = new Set();
|
|
563
|
+
const out = [];
|
|
564
|
+
for (const opt of initialFlat) {
|
|
565
|
+
const key = valueKey(opt.value);
|
|
566
|
+
if (seen.has(key) || !matches(opt)) {
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
seen.add(key);
|
|
570
|
+
out.push(opt);
|
|
571
|
+
}
|
|
572
|
+
if (modelType) {
|
|
573
|
+
for (const opt of searchResults) {
|
|
574
|
+
const key = valueKey(opt.value);
|
|
575
|
+
if (seen.has(key) || !matches(opt)) {
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
seen.add(key);
|
|
579
|
+
out.push(opt);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return out;
|
|
583
|
+
}, [modelType, searchResults, searchQuery, initialFlat]);
|
|
584
|
+
/*
|
|
585
|
+
* Filter out the currently-selected key(s) ONLY in multi-select. For
|
|
586
|
+
* single-select the user needs to see the current value too so they can
|
|
587
|
+
* compare and switch — react-select keeps the selected option visible
|
|
588
|
+
* (just highlights it), so we match that.
|
|
589
|
+
*/
|
|
590
|
+
const availableOptions = useMemo(() => {
|
|
591
|
+
if (!isMulti) {
|
|
592
|
+
return optionsList;
|
|
593
|
+
}
|
|
594
|
+
const selectedSet = new Set(selectedKeys);
|
|
595
|
+
return optionsList.filter((opt) => {
|
|
596
|
+
return !selectedSet.has(valueKey(opt.value));
|
|
597
|
+
});
|
|
598
|
+
}, [optionsList, selectedKeys, isMulti]);
|
|
599
|
+
// Clamp the keyboard cursor when the visible list shrinks.
|
|
600
|
+
useEffect(() => {
|
|
601
|
+
const len = activeTab === "labels" ? filteredLabels.length : availableOptions.length;
|
|
602
|
+
if (highlightedIndex >= len) {
|
|
603
|
+
setHighlightedIndex(len - 1);
|
|
604
|
+
}
|
|
605
|
+
}, [availableOptions, filteredLabels, highlightedIndex, activeTab]);
|
|
606
|
+
const notify = useCallback((next) => {
|
|
607
|
+
if (!props.onChange) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (isMulti) {
|
|
611
|
+
props.onChange(next);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
props.onChange(next.length > 0 ? next[0] : null);
|
|
615
|
+
}, [isMulti, props.onChange]);
|
|
616
|
+
const addOption = (opt) => {
|
|
617
|
+
var _a;
|
|
618
|
+
const key = valueKey(opt.value);
|
|
619
|
+
optionsCacheRef.current.set(key, opt);
|
|
620
|
+
if (isMulti) {
|
|
621
|
+
if (selectedKeys.includes(key)) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const next = [...selectedKeys, key];
|
|
625
|
+
setSelectedKeys(next);
|
|
626
|
+
notify(next);
|
|
627
|
+
setSearchQuery("");
|
|
628
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
setSelectedKeys([key]);
|
|
632
|
+
notify([key]);
|
|
633
|
+
setSearchQuery("");
|
|
634
|
+
setIsOpen(false);
|
|
635
|
+
};
|
|
636
|
+
const removeKey = (key) => {
|
|
637
|
+
const next = selectedKeys.filter((k) => {
|
|
638
|
+
return k !== key;
|
|
639
|
+
});
|
|
640
|
+
setSelectedKeys(next);
|
|
641
|
+
notify(next);
|
|
642
|
+
};
|
|
643
|
+
const clearAll = () => {
|
|
644
|
+
setSelectedKeys([]);
|
|
645
|
+
notify([]);
|
|
646
|
+
};
|
|
647
|
+
/*
|
|
648
|
+
* Bulk-add via labels: fetch every entity tagged with any selected label
|
|
649
|
+
* and merge into the current selection. Caps generous (LIMIT_PER_PROJECT)
|
|
650
|
+
* since this is an intentional bulk action, not a typeahead.
|
|
651
|
+
*/
|
|
652
|
+
const applyLabelSelection = async () => {
|
|
653
|
+
if (!modelType || selectedLabelIds.length === 0) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
setIsApplyingLabels(true);
|
|
657
|
+
setLabelError("");
|
|
658
|
+
try {
|
|
659
|
+
const baseSelect = {
|
|
660
|
+
_id: true,
|
|
661
|
+
[labelField]: true,
|
|
662
|
+
};
|
|
663
|
+
if (colorField) {
|
|
664
|
+
baseSelect[colorField] = true;
|
|
665
|
+
}
|
|
666
|
+
const result = await ModelAPI.getList({
|
|
667
|
+
modelType: modelType,
|
|
668
|
+
query: {
|
|
669
|
+
labels: new Includes(selectedLabelIds),
|
|
670
|
+
},
|
|
671
|
+
limit: LIMIT_PER_PROJECT,
|
|
672
|
+
skip: 0,
|
|
673
|
+
select: baseSelect,
|
|
674
|
+
sort: { [labelField]: SortOrder.Ascending },
|
|
675
|
+
});
|
|
676
|
+
const existing = new Set(selectedKeys);
|
|
677
|
+
const additions = [];
|
|
678
|
+
for (const item of result.data) {
|
|
679
|
+
const opt = modelToOption(item);
|
|
680
|
+
if (!opt) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
const key = valueKey(opt.value);
|
|
684
|
+
optionsCacheRef.current.set(key, opt);
|
|
685
|
+
if (!existing.has(key)) {
|
|
686
|
+
existing.add(key);
|
|
687
|
+
additions.push(key);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (additions.length === 0) {
|
|
691
|
+
setLabelError("No new entries matched the selected labels (or you don't have read access).");
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const next = [...selectedKeys, ...additions];
|
|
695
|
+
setSelectedKeys(next);
|
|
696
|
+
notify(next);
|
|
697
|
+
setSelectedLabelIds([]);
|
|
698
|
+
setSearchQuery("");
|
|
699
|
+
setActiveTab("options");
|
|
700
|
+
setIsOpen(false);
|
|
701
|
+
}
|
|
702
|
+
catch (_a) {
|
|
703
|
+
setLabelError("Failed to fetch entries for the selected labels.");
|
|
704
|
+
}
|
|
705
|
+
finally {
|
|
706
|
+
setIsApplyingLabels(false);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const toggleLabelId = (id) => {
|
|
710
|
+
setSelectedLabelIds((prev) => {
|
|
711
|
+
if (prev.includes(id)) {
|
|
712
|
+
return prev.filter((x) => {
|
|
713
|
+
return x !== id;
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
return [...prev, id];
|
|
717
|
+
});
|
|
718
|
+
};
|
|
719
|
+
const fetchLabelPreview = async (labelId) => {
|
|
720
|
+
if (!modelType) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
setLoadingLabelIds((prev) => {
|
|
724
|
+
const next = new Set(prev);
|
|
725
|
+
next.add(labelId);
|
|
726
|
+
return next;
|
|
727
|
+
});
|
|
728
|
+
setLabelLoadErrors((prev) => {
|
|
729
|
+
const next = Object.assign({}, prev);
|
|
730
|
+
delete next[labelId];
|
|
731
|
+
return next;
|
|
732
|
+
});
|
|
733
|
+
try {
|
|
734
|
+
const baseSelect = {
|
|
735
|
+
_id: true,
|
|
736
|
+
[labelField]: true,
|
|
737
|
+
};
|
|
738
|
+
if (colorField) {
|
|
739
|
+
baseSelect[colorField] = true;
|
|
740
|
+
}
|
|
741
|
+
const result = await ModelAPI.getList({
|
|
742
|
+
modelType: modelType,
|
|
743
|
+
query: {
|
|
744
|
+
labels: new Includes([labelId]),
|
|
745
|
+
},
|
|
746
|
+
limit: LABEL_PREVIEW_LIMIT,
|
|
747
|
+
skip: 0,
|
|
748
|
+
select: baseSelect,
|
|
749
|
+
sort: { [labelField]: SortOrder.Ascending },
|
|
750
|
+
});
|
|
751
|
+
const items = [];
|
|
752
|
+
for (const item of result.data) {
|
|
753
|
+
const opt = modelToOption(item);
|
|
754
|
+
if (opt) {
|
|
755
|
+
items.push(opt);
|
|
756
|
+
optionsCacheRef.current.set(valueKey(opt.value), opt);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
setResourcesByLabel((prev) => {
|
|
760
|
+
return Object.assign(Object.assign({}, prev), { [labelId]: items });
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
catch (_a) {
|
|
764
|
+
setLabelLoadErrors((prev) => {
|
|
765
|
+
return Object.assign(Object.assign({}, prev), { [labelId]: "Failed to load entries." });
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
finally {
|
|
769
|
+
setLoadingLabelIds((prev) => {
|
|
770
|
+
const next = new Set(prev);
|
|
771
|
+
next.delete(labelId);
|
|
772
|
+
return next;
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
const toggleLabelExpansion = (labelId) => {
|
|
777
|
+
setExpandedLabelIds((prev) => {
|
|
778
|
+
const next = new Set(prev);
|
|
779
|
+
if (next.has(labelId)) {
|
|
780
|
+
next.delete(labelId);
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
next.add(labelId);
|
|
784
|
+
if (resourcesByLabel[labelId] === undefined) {
|
|
785
|
+
void fetchLabelPreview(labelId);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return next;
|
|
789
|
+
});
|
|
790
|
+
};
|
|
791
|
+
const selectedOptions = useMemo(() => {
|
|
792
|
+
return selectedKeys.map((key) => {
|
|
793
|
+
const cached = optionsCacheRef.current.get(key);
|
|
794
|
+
if (cached) {
|
|
795
|
+
return cached;
|
|
796
|
+
}
|
|
797
|
+
return { value: key, label: key };
|
|
798
|
+
});
|
|
799
|
+
/*
|
|
800
|
+
* Cache is mutated during render based on initialFlat / inboundOptions
|
|
801
|
+
* (see seed loops above), so include them here to make the dependency
|
|
802
|
+
* graph honest — when the parent hands us new options, this memo
|
|
803
|
+
* recomputes against the freshly-seeded cache.
|
|
804
|
+
*/
|
|
805
|
+
}, [selectedKeys, initialFlat, inboundOptions]);
|
|
806
|
+
/*
|
|
807
|
+
* Visual label color extracted from option.color (which can be either a
|
|
808
|
+
* Color instance or a string). Used to render a small dot next to each
|
|
809
|
+
* row that has one.
|
|
810
|
+
*/
|
|
811
|
+
const optionColorString = (opt) => {
|
|
812
|
+
const c = opt.color;
|
|
813
|
+
if (!c) {
|
|
814
|
+
return undefined;
|
|
815
|
+
}
|
|
816
|
+
if (typeof c === "string") {
|
|
817
|
+
return c;
|
|
818
|
+
}
|
|
819
|
+
if (typeof c.toString === "function") {
|
|
820
|
+
return c.toString();
|
|
821
|
+
}
|
|
822
|
+
return undefined;
|
|
823
|
+
};
|
|
824
|
+
const labelColorString = (label) => {
|
|
825
|
+
const c = label.color;
|
|
826
|
+
if (!c) {
|
|
827
|
+
return undefined;
|
|
828
|
+
}
|
|
829
|
+
if (typeof c === "string") {
|
|
830
|
+
return c;
|
|
831
|
+
}
|
|
832
|
+
if (typeof c.toString === "function") {
|
|
833
|
+
return c.toString();
|
|
834
|
+
}
|
|
835
|
+
return undefined;
|
|
836
|
+
};
|
|
837
|
+
/*
|
|
838
|
+
* Render the *current selection* as either a chip row (multi) or the
|
|
839
|
+
* single value in-place when the popover is closed (single). When the
|
|
840
|
+
* popover is open the input takes over for typing.
|
|
841
|
+
*/
|
|
842
|
+
const placeholderText = props.placeholder || "Select...";
|
|
843
|
+
const showSingleSelectedText = !isMulti && !isOpen && selectedOptions.length > 0;
|
|
844
|
+
return (React.createElement("div", { ref: containerRef, id: props.id, className: props.className || "relative mt-2 mb-1 w-full" },
|
|
845
|
+
isMulti && selectedOptions.length > 0 && (React.createElement("div", { className: "mb-2 flex flex-wrap gap-1.5" }, selectedOptions.map((opt) => {
|
|
846
|
+
const key = valueKey(opt.value);
|
|
847
|
+
const colorStr = optionColorString(opt);
|
|
848
|
+
return (React.createElement("span", { key: key, className: "inline-flex items-center gap-1.5 rounded-md border border-indigo-100 bg-indigo-50 px-2 py-1 text-xs font-medium text-indigo-900" },
|
|
849
|
+
colorStr && (React.createElement("span", { "aria-hidden": "true", className: "inline-block h-2 w-2 rounded-full", style: { backgroundColor: colorStr } })),
|
|
850
|
+
React.createElement("span", { className: "max-w-[14rem] truncate" }, opt.label),
|
|
851
|
+
!props.disabled && (React.createElement("button", { type: "button", "aria-label": `Remove ${opt.label}`, onClick: () => {
|
|
852
|
+
removeKey(key);
|
|
853
|
+
}, className: "ml-0.5 rounded-full text-indigo-400 transition-colors hover:bg-indigo-100 hover:text-indigo-700 focus:outline-none focus:ring-1 focus:ring-indigo-500" },
|
|
854
|
+
React.createElement("svg", { className: "h-3 w-3", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" },
|
|
855
|
+
React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }))))));
|
|
856
|
+
}))),
|
|
857
|
+
React.createElement("div", { className: "relative" },
|
|
858
|
+
showSingleSelectedText && (React.createElement("button", { type: "button", disabled: props.disabled, onClick: () => {
|
|
859
|
+
var _a;
|
|
860
|
+
if (props.disabled) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
setIsOpen(true);
|
|
864
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
865
|
+
}, onFocus: () => {
|
|
866
|
+
var _a;
|
|
867
|
+
(_a = props.onFocus) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
868
|
+
}, className: `flex w-full items-center justify-between rounded-lg border bg-white px-3 py-2 text-left text-sm shadow-sm transition-colors ${props.error
|
|
869
|
+
? "border-red-400"
|
|
870
|
+
: "border-gray-300 hover:border-indigo-300"} ${props.disabled
|
|
871
|
+
? "cursor-not-allowed bg-gray-100 text-gray-400"
|
|
872
|
+
: ""}` },
|
|
873
|
+
React.createElement("span", { className: "flex items-center gap-2" },
|
|
874
|
+
(() => {
|
|
875
|
+
const colorStr = optionColorString(selectedOptions[0]);
|
|
876
|
+
if (!colorStr) {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
return (React.createElement("span", { "aria-hidden": "true", className: "inline-block h-2.5 w-2.5 rounded-full border border-gray-200", style: { backgroundColor: colorStr } }));
|
|
880
|
+
})(),
|
|
881
|
+
React.createElement("span", { className: "font-medium text-gray-900" }, selectedOptions[0].label)),
|
|
882
|
+
React.createElement("div", { className: "flex items-center gap-1 text-gray-400" },
|
|
883
|
+
!props.disabled && (React.createElement("button", { type: "button", "aria-label": "Clear selection", onClick: (e) => {
|
|
884
|
+
e.stopPropagation();
|
|
885
|
+
clearAll();
|
|
886
|
+
}, className: "rounded p-0.5 hover:bg-gray-100 hover:text-red-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" },
|
|
887
|
+
React.createElement("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" },
|
|
888
|
+
React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" })))),
|
|
889
|
+
React.createElement(Icon, { icon: IconProp.ChevronDown, className: "h-4 w-4" })))),
|
|
890
|
+
!showSingleSelectedText && (React.createElement("div", { className: `relative rounded-lg border bg-white shadow-sm transition-colors ${props.error
|
|
891
|
+
? "border-red-400 ring-2 ring-red-100"
|
|
892
|
+
: isOpen
|
|
893
|
+
? "border-indigo-400 ring-2 ring-indigo-100"
|
|
894
|
+
: "border-gray-300 hover:border-indigo-300"} ${props.disabled ? "bg-gray-100" : ""}` },
|
|
895
|
+
React.createElement("input", { ref: inputRef, type: "text", value: searchQuery, disabled: props.disabled, tabIndex: props.tabIndex, "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-label": props.ariaLabel, "aria-invalid": props.error ? true : undefined, "data-testid": props.dataTestId, role: "combobox", placeholder: isMulti && selectedOptions.length > 0
|
|
896
|
+
? "Search to add more..."
|
|
897
|
+
: placeholderText, onChange: (event) => {
|
|
898
|
+
setSearchQuery(event.target.value);
|
|
899
|
+
setIsOpen(true);
|
|
900
|
+
setHighlightedIndex(-1);
|
|
901
|
+
}, onFocus: () => {
|
|
902
|
+
var _a;
|
|
903
|
+
setIsOpen(true);
|
|
904
|
+
(_a = props.onFocus) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
905
|
+
}, onBlur: () => {
|
|
906
|
+
var _a;
|
|
907
|
+
(_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
908
|
+
}, onKeyDown: (event) => {
|
|
909
|
+
const activeLen = activeTab === "labels"
|
|
910
|
+
? filteredLabels.length
|
|
911
|
+
: availableOptions.length;
|
|
912
|
+
if (event.key === "ArrowDown") {
|
|
913
|
+
if (activeLen === 0) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
event.preventDefault();
|
|
917
|
+
setIsOpen(true);
|
|
918
|
+
setHighlightedIndex((prev) => {
|
|
919
|
+
const next = prev + 1;
|
|
920
|
+
return next >= activeLen ? 0 : next;
|
|
921
|
+
});
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (event.key === "ArrowUp") {
|
|
925
|
+
if (activeLen === 0) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
event.preventDefault();
|
|
929
|
+
setIsOpen(true);
|
|
930
|
+
setHighlightedIndex((prev) => {
|
|
931
|
+
if (prev <= 0) {
|
|
932
|
+
return activeLen - 1;
|
|
933
|
+
}
|
|
934
|
+
return prev - 1;
|
|
935
|
+
});
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (event.key === "Enter") {
|
|
939
|
+
if (highlightedIndex < 0 || highlightedIndex >= activeLen) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
event.preventDefault();
|
|
943
|
+
if (activeTab === "labels") {
|
|
944
|
+
const label = filteredLabels[highlightedIndex];
|
|
945
|
+
const labelId = (label === null || label === void 0 ? void 0 : label._id) ? String(label._id) : "";
|
|
946
|
+
if (labelId) {
|
|
947
|
+
toggleLabelId(labelId);
|
|
948
|
+
}
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const opt = availableOptions[highlightedIndex];
|
|
952
|
+
if (opt) {
|
|
953
|
+
addOption(opt);
|
|
954
|
+
setHighlightedIndex(-1);
|
|
955
|
+
}
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
if (event.key === "Escape") {
|
|
959
|
+
setIsOpen(false);
|
|
960
|
+
setHighlightedIndex(-1);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (event.key === "Backspace" &&
|
|
964
|
+
searchQuery === "" &&
|
|
965
|
+
isMulti &&
|
|
966
|
+
selectedKeys.length > 0 &&
|
|
967
|
+
activeTab === "options") {
|
|
968
|
+
event.preventDefault();
|
|
969
|
+
removeKey(selectedKeys[selectedKeys.length - 1]);
|
|
970
|
+
}
|
|
971
|
+
}, className: "block w-full rounded-lg bg-transparent px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:text-gray-500" }),
|
|
972
|
+
!isMulti && selectedKeys.length > 0 && !props.disabled && (React.createElement("button", { type: "button", "aria-label": "Clear selection", onClick: () => {
|
|
973
|
+
var _a;
|
|
974
|
+
clearAll();
|
|
975
|
+
setSearchQuery("");
|
|
976
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
977
|
+
}, className: "absolute inset-y-0 right-2 my-auto flex h-6 w-6 items-center justify-center rounded text-gray-400 transition-colors hover:bg-gray-100 hover:text-red-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" },
|
|
978
|
+
React.createElement("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" },
|
|
979
|
+
React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }))))))),
|
|
980
|
+
isOpen && !props.disabled && (React.createElement("div", { className: "absolute z-20 mt-1 flex max-h-96 w-full flex-col overflow-hidden rounded-md border border-gray-200 bg-white text-sm shadow-lg", role: "listbox" },
|
|
981
|
+
labelsTabEnabled && (React.createElement("div", { className: "flex flex-shrink-0 items-center gap-1 border-b border-gray-100 bg-gray-50 px-1.5 py-1" },
|
|
982
|
+
React.createElement("button", { type: "button", role: "tab", "aria-selected": activeTab === "options", onMouseDown: (event) => {
|
|
983
|
+
event.preventDefault();
|
|
984
|
+
}, onClick: () => {
|
|
985
|
+
setActiveTab("options");
|
|
986
|
+
setHighlightedIndex(-1);
|
|
987
|
+
}, className: `rounded px-2.5 py-1 text-xs font-medium transition-colors focus:outline-none focus:ring-1 focus:ring-indigo-500 ${activeTab === "options"
|
|
988
|
+
? "bg-white text-indigo-700 shadow-sm ring-1 ring-gray-200"
|
|
989
|
+
: "text-gray-600 hover:bg-white/60 hover:text-gray-800"}` }, "Results"),
|
|
990
|
+
React.createElement("button", { type: "button", role: "tab", "aria-selected": activeTab === "labels", onMouseDown: (event) => {
|
|
991
|
+
event.preventDefault();
|
|
992
|
+
}, onClick: () => {
|
|
993
|
+
setActiveTab("labels");
|
|
994
|
+
setHighlightedIndex(-1);
|
|
995
|
+
}, className: `inline-flex items-center gap-1.5 rounded px-2.5 py-1 text-xs font-medium transition-colors focus:outline-none focus:ring-1 focus:ring-indigo-500 ${activeTab === "labels"
|
|
996
|
+
? "bg-white text-indigo-700 shadow-sm ring-1 ring-gray-200"
|
|
997
|
+
: "text-gray-600 hover:bg-white/60 hover:text-gray-800"}` },
|
|
998
|
+
React.createElement(Icon, { icon: IconProp.Tag, className: "h-3.5 w-3.5" }),
|
|
999
|
+
"Labels",
|
|
1000
|
+
selectedLabelIds.length > 0 && (React.createElement("span", { className: "inline-flex h-4 min-w-[16px] items-center justify-center rounded-full bg-indigo-100 px-1 text-[10px] font-semibold text-indigo-700" }, selectedLabelIds.length))),
|
|
1001
|
+
React.createElement("span", { className: "ml-auto pr-1 text-[11px] text-gray-400" }, activeTab === "options"
|
|
1002
|
+
? "Pick individually"
|
|
1003
|
+
: "Bulk-add by tag"))),
|
|
1004
|
+
activeTab === "options" && (React.createElement("div", { className: "flex-1 overflow-auto py-1" },
|
|
1005
|
+
isLoading && (React.createElement("div", { className: "flex items-center px-3 py-2 text-gray-500" },
|
|
1006
|
+
React.createElement("svg", { className: "animate-spin -ml-0.5 mr-2 h-4 w-4 text-indigo-500", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-hidden": "true" },
|
|
1007
|
+
React.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
1008
|
+
React.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })),
|
|
1009
|
+
React.createElement("span", null, "Searching..."))),
|
|
1010
|
+
!isLoading && availableOptions.length === 0 && (React.createElement("div", { className: "px-3 py-2 text-gray-500" }, searchQuery.trim() === ""
|
|
1011
|
+
? "No options."
|
|
1012
|
+
: "No matching entries.")),
|
|
1013
|
+
!isLoading &&
|
|
1014
|
+
availableOptions.map((opt, idx) => {
|
|
1015
|
+
const key = valueKey(opt.value);
|
|
1016
|
+
const isHighlighted = idx === highlightedIndex;
|
|
1017
|
+
const isCurrentSelection = !isMulti && selectedKeys[0] === key;
|
|
1018
|
+
const colorStr = optionColorString(opt);
|
|
1019
|
+
return (React.createElement("button", { key: key, type: "button", role: "option", "aria-selected": isCurrentSelection || isHighlighted, onMouseEnter: () => {
|
|
1020
|
+
setHighlightedIndex(idx);
|
|
1021
|
+
}, onMouseDown: (event) => {
|
|
1022
|
+
event.preventDefault();
|
|
1023
|
+
}, onClick: () => {
|
|
1024
|
+
addOption(opt);
|
|
1025
|
+
}, className: `flex w-full items-center gap-2 px-3 py-2 text-left ${isHighlighted
|
|
1026
|
+
? "bg-indigo-600 text-white"
|
|
1027
|
+
: isCurrentSelection
|
|
1028
|
+
? "bg-indigo-50 text-indigo-900"
|
|
1029
|
+
: "text-gray-700 hover:bg-indigo-50"}` },
|
|
1030
|
+
colorStr && (React.createElement("span", { "aria-hidden": "true", className: "inline-block h-2.5 w-2.5 flex-shrink-0 rounded-full border border-gray-200", style: { backgroundColor: colorStr } })),
|
|
1031
|
+
React.createElement("span", { className: "truncate" }, opt.label),
|
|
1032
|
+
opt.description && (React.createElement("span", { className: `ml-auto truncate text-xs ${isHighlighted
|
|
1033
|
+
? "text-indigo-100"
|
|
1034
|
+
: "text-gray-500"}` }, opt.description)),
|
|
1035
|
+
isCurrentSelection && (React.createElement("svg", { className: `ml-auto h-4 w-4 flex-shrink-0 ${isHighlighted ? "text-white" : "text-indigo-600"}`, viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" },
|
|
1036
|
+
React.createElement("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" })))));
|
|
1037
|
+
}))),
|
|
1038
|
+
activeTab === "labels" && (React.createElement("div", { className: "flex-1 overflow-auto py-1" },
|
|
1039
|
+
isLoadingLabels && (React.createElement("div", { className: "flex items-center px-3 py-2 text-gray-500" },
|
|
1040
|
+
React.createElement("svg", { className: "animate-spin -ml-0.5 mr-2 h-4 w-4 text-indigo-500", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-hidden": "true" },
|
|
1041
|
+
React.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
1042
|
+
React.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })),
|
|
1043
|
+
React.createElement("span", null, "Loading labels..."))),
|
|
1044
|
+
!isLoadingLabels && labelError !== "" && (React.createElement("div", { className: "px-3 py-2 text-red-600" }, labelError)),
|
|
1045
|
+
!isLoadingLabels &&
|
|
1046
|
+
labelError === "" &&
|
|
1047
|
+
labelsLoaded &&
|
|
1048
|
+
allLabels.length === 0 && (React.createElement("div", { className: "px-3 py-2 text-gray-500" }, "No labels found in this project. Create labels first to use this shortcut.")),
|
|
1049
|
+
!isLoadingLabels &&
|
|
1050
|
+
labelError === "" &&
|
|
1051
|
+
labelsLoaded &&
|
|
1052
|
+
allLabels.length > 0 &&
|
|
1053
|
+
filteredLabels.length === 0 && (React.createElement("div", { className: "px-3 py-2 text-gray-500" },
|
|
1054
|
+
"No labels match \u201C",
|
|
1055
|
+
searchQuery.trim(),
|
|
1056
|
+
"\u201D.")),
|
|
1057
|
+
!isLoadingLabels &&
|
|
1058
|
+
filteredLabels.map((label, idx) => {
|
|
1059
|
+
const labelId = label._id ? String(label._id) : "";
|
|
1060
|
+
if (!labelId) {
|
|
1061
|
+
return React.createElement("span", { key: `empty-${idx}` });
|
|
1062
|
+
}
|
|
1063
|
+
const isChecked = selectedLabelIds.includes(labelId);
|
|
1064
|
+
const isHighlighted = idx === highlightedIndex;
|
|
1065
|
+
const isExpanded = expandedLabelIds.has(labelId);
|
|
1066
|
+
const isLoadingPreview = loadingLabelIds.has(labelId);
|
|
1067
|
+
const preview = resourcesByLabel[labelId];
|
|
1068
|
+
const previewError = labelLoadErrors[labelId];
|
|
1069
|
+
const colorStr = labelColorString(label);
|
|
1070
|
+
return (React.createElement("div", { key: labelId, onMouseEnter: () => {
|
|
1071
|
+
setHighlightedIndex(idx);
|
|
1072
|
+
}, className: `border-b border-gray-100 last:border-b-0 ${isHighlighted ? "bg-indigo-50" : ""}` },
|
|
1073
|
+
React.createElement("div", { className: "flex w-full items-center gap-2 px-2 py-1.5" },
|
|
1074
|
+
React.createElement("button", { type: "button", "aria-label": isExpanded ? "Collapse entries" : "Expand entries", "aria-expanded": isExpanded, onMouseDown: (event) => {
|
|
1075
|
+
event.preventDefault();
|
|
1076
|
+
}, onClick: () => {
|
|
1077
|
+
toggleLabelExpansion(labelId);
|
|
1078
|
+
}, className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded text-gray-400 hover:bg-gray-200 hover:text-gray-700 focus:outline-none focus:ring-1 focus:ring-indigo-500" },
|
|
1079
|
+
React.createElement(Icon, { icon: IconProp.ChevronRight, className: `h-3.5 w-3.5 transition-transform duration-150 ${isExpanded ? "rotate-90" : ""}` })),
|
|
1080
|
+
React.createElement("button", { type: "button", role: "option", "aria-selected": isChecked, onMouseDown: (event) => {
|
|
1081
|
+
event.preventDefault();
|
|
1082
|
+
}, onClick: () => {
|
|
1083
|
+
toggleLabelId(labelId);
|
|
1084
|
+
}, className: `flex flex-1 items-center gap-2 rounded px-1 py-1 text-left ${isHighlighted
|
|
1085
|
+
? "text-gray-900"
|
|
1086
|
+
: "text-gray-700 hover:bg-indigo-100/50"}` },
|
|
1087
|
+
React.createElement("span", { "aria-hidden": "true", className: `flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border ${isChecked
|
|
1088
|
+
? "border-indigo-600 bg-indigo-600 text-white"
|
|
1089
|
+
: "border-gray-300 bg-white"}` }, isChecked && (React.createElement("svg", { className: "h-3 w-3", viewBox: "0 0 20 20", fill: "currentColor" },
|
|
1090
|
+
React.createElement("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" })))),
|
|
1091
|
+
colorStr && (React.createElement("span", { className: "inline-block h-3 w-3 flex-shrink-0 rounded-full", style: { backgroundColor: colorStr }, "aria-hidden": "true" })),
|
|
1092
|
+
React.createElement("span", { className: "truncate" }, label.name || "Unnamed Label")),
|
|
1093
|
+
preview !== undefined && (React.createElement("span", { className: "flex-shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-[10px] font-medium text-gray-600" },
|
|
1094
|
+
preview.length,
|
|
1095
|
+
preview.length >= LABEL_PREVIEW_LIMIT ? "+" : ""))),
|
|
1096
|
+
isExpanded && (React.createElement("div", { className: "border-t border-gray-100 bg-gray-50 px-3 py-2 pl-10" }, isLoadingPreview ? (React.createElement("div", { className: "flex items-center gap-2 py-1 text-xs text-gray-500" },
|
|
1097
|
+
React.createElement("svg", { className: "h-3.5 w-3.5 animate-spin text-indigo-500", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-hidden": "true" },
|
|
1098
|
+
React.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
1099
|
+
React.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })),
|
|
1100
|
+
React.createElement("span", null, "Loading entries..."))) : previewError ? (React.createElement("div", { className: "py-1 text-xs text-red-600" }, previewError)) : !preview || preview.length === 0 ? (React.createElement("div", { className: "py-1 text-xs italic text-gray-500" }, "No entries tagged with this label.")) : (React.createElement("div", { className: "flex flex-wrap gap-1" }, preview.map((item) => {
|
|
1101
|
+
return (React.createElement("span", { key: valueKey(item.value), className: "inline-flex items-center gap-1 rounded border border-gray-200 bg-white px-1.5 py-0.5 text-[11px] text-gray-700" },
|
|
1102
|
+
React.createElement("span", { className: "max-w-[10rem] truncate" }, item.label)));
|
|
1103
|
+
})))))));
|
|
1104
|
+
}))),
|
|
1105
|
+
activeTab === "labels" && selectedLabelIds.length > 0 && (React.createElement("div", { className: "flex flex-shrink-0 items-center justify-between gap-2 border-t border-gray-100 bg-gray-50 px-2 py-1.5" },
|
|
1106
|
+
React.createElement("button", { type: "button", onMouseDown: (event) => {
|
|
1107
|
+
event.preventDefault();
|
|
1108
|
+
}, onClick: () => {
|
|
1109
|
+
setSelectedLabelIds([]);
|
|
1110
|
+
}, disabled: isApplyingLabels, className: "rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-white hover:text-gray-800 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:opacity-50" }, "Clear"),
|
|
1111
|
+
React.createElement("button", { type: "button", onMouseDown: (event) => {
|
|
1112
|
+
event.preventDefault();
|
|
1113
|
+
}, onClick: () => {
|
|
1114
|
+
void applyLabelSelection();
|
|
1115
|
+
}, disabled: isApplyingLabels, className: "inline-flex items-center gap-1.5 rounded-md bg-indigo-600 px-3 py-1 text-xs font-semibold text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1 disabled:opacity-60" },
|
|
1116
|
+
isApplyingLabels && (React.createElement("svg", { className: "h-3.5 w-3.5 animate-spin", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-hidden": "true" },
|
|
1117
|
+
React.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
1118
|
+
React.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" }))),
|
|
1119
|
+
isApplyingLabels
|
|
1120
|
+
? "Adding..."
|
|
1121
|
+
: `Add entries from ${selectedLabelIds.length} label${selectedLabelIds.length === 1 ? "" : "s"}`))))),
|
|
1122
|
+
props.error && (React.createElement("p", { className: "mt-1 text-sm text-red-400", role: "alert" }, props.error))));
|
|
1123
|
+
};
|
|
1124
|
+
export default EntityDropdown;
|
|
1125
|
+
//# sourceMappingURL=EntityDropdown.js.map
|