@ExecuteOn(TaskExecutors.IO)
    @Get(uri = "/search")
    @Operation(tags = {"Triggers"}, summary = "Search for triggers")
    public PagedResults<Triggers> search(
        @Parameter(description = "The current page") @QueryValue(defaultValue = "1") int page,
        @Parameter(description = "The current page size") @QueryValue(defaultValue = "10") int size,
        @Parameter(description = "The sort of current page") @Nullable @QueryValue List<String> sort,
        @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
        @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
        @Parameter(description = "The identifier of the worker currently evaluating the trigger") @Nullable @QueryValue String workerId
    ) throws HttpStatusException {

        ArrayListTotal<Trigger> triggerContexts = triggerRepository.find(
            PageableUtils.from(page, size, sort, triggerRepository.sortMapping()),
            query,
            tenantService.resolveTenant(),
            namespace,
            null,
            workerId
        );

        List<Triggers> triggers = new ArrayList<>();
        triggerContexts.forEach(tc -> {
            Optional<Flow> flow = flowRepository.findById(tc.getTenantId(), tc.getNamespace(), tc.getFlowId());
            if (flow.isEmpty()) {
                // Warn instead of throwing to avoid blocking the trigger UI
                log.warn(String.format("Flow %s not found for trigger %s", tc.getFlowId(), tc.getTriggerId()));
                triggers.add(Triggers.builder()
                    .abstractTrigger(null)
                    .triggerContext(tc)
                    .build()
                );

                return;
            }

            if (flow.get().getTriggers() == null) {
                // a trigger was removed from the flow but still in the trigger table
                return;
            }

            AbstractTrigger abstractTrigger = flow.get().getTriggers().stream().filter(t -> t.getId().equals(tc.getTriggerId())).findFirst().orElse(null);
            if (abstractTrigger == null) {
                // Warn instead of throwing to avoid blocking the trigger UI
                log.warn(String.format("Flow %s has no trigger %s", tc.getFlowId(), tc.getTriggerId()));
            }

            triggers.add(Triggers.builder()
                .abstractTrigger(abstractTrigger)
                .triggerContext(tc)
                .build()
            );
        });

        return PagedResults.of(new ArrayListTotal<>(triggers, triggerContexts.getTotal()));
    }
@SuppressWarnings("unchecked")
    @Test
    void search() {
        String triggerFlowId = "schedule-trigger-search";
        String triggerNamespace = "io.kestra.tests.schedule";

        Flow flow = generateFlow(triggerFlowId);
        jdbcFlowRepository.create(flow, flow.generateSource(), flow);

        Trigger trigger = Trigger.builder()
            .flowId(triggerFlowId)
            .namespace(triggerNamespace)
            .triggerId("trigger-nextexec-schedule")
            .date(ZonedDateTime.now())
            .build();

        jdbcTriggerRepository.save(trigger);
        jdbcTriggerRepository.save(trigger.toBuilder().triggerId("trigger-nextexec-polling").build());

        PagedResults<TriggerController.Triggers> triggers = client.toBlocking().retrieve(HttpRequest.GET("/api/v1/triggers/search?q=schedule-trigger-search&namespace=io.kestra.tests&sort=triggerId:asc"), Argument.of(PagedResults.class, TriggerController.Triggers.class));
        assertThat(triggers.getTotal(), greaterThanOrEqualTo(2L));

        assertThat(triggers.getResults().stream().map(TriggerController.Triggers::getTriggerContext).toList(), Matchers.hasItems(
                allOf(
                    hasProperty("triggerId", is("trigger-nextexec-schedule")),
                    hasProperty("namespace", is(triggerNamespace)),
                    hasProperty("flowId", is(triggerFlowId))
                ),
                allOf(
                    hasProperty("triggerId", is("trigger-nextexec-polling")),
                    hasProperty("namespace", is(triggerNamespace)),
                    hasProperty("flowId", is(triggerFlowId))
                )
            )
        );
    }