From 3c6c1a043de9c84bdddb3c4bcc067629ad2ac83f Mon Sep 17 00:00:00 2001 From: shuan Date: Wed, 27 May 2026 22:51:22 +0800 Subject: [PATCH 1/6] feat: filter api --- src/form/question/models.tsp | 28 ++++++++++++++++++ src/form/question/operations.tsp | 13 ++++++++- src/form/response/models.tsp | 50 ++++++++++++++++++++++++++++++++ src/form/response/operations.tsp | 13 +++++++++ src/form/view/models.tsp | 18 ++++++++++++ 5 files changed, 121 insertions(+), 1 deletion(-) diff --git a/src/form/question/models.tsp b/src/form/question/models.tsp index 5aece472..8973dbd0 100644 --- a/src/form/question/models.tsp +++ b/src/form/question/models.tsp @@ -402,4 +402,32 @@ namespace CoreSystem.Forms { @doc("If set, choices are dynamically generated from answers to this question.") sourceId?: uuid; } + + @doc("A single answer option available for filtering.") + model FilterAnswerItem { + @doc("The text displayed to the user in the filter UI.") + displayValue: string; + + @doc("The raw value used for backend querying and matching.") + matchValue: string; + + @doc("Whether this filter option is selected. Defaults to false from server.") + selected: boolean; + } + + @doc("Response model for getting question filters.") + @example(#{ + formId: "550e8400-e29b-41d4-a716-446655440000", + answers: #[ + #{ displayValue: "開發部", matchValue: "abc-123-def", selected: false }, + #{ displayValue: "品牌部", matchValue: "ghi-456-jkl", selected: false } + ] + }) + model QuestionFiltersResponse { + @doc("The unique identifier of the form.") + formId: uuid; + + @doc("The list of available unique answers for this question.") + answers: FilterAnswerItem[]; + } } diff --git a/src/form/question/operations.tsp b/src/form/question/operations.tsp index 068f1277..81b934e5 100644 --- a/src/form/question/operations.tsp +++ b/src/form/question/operations.tsp @@ -37,4 +37,15 @@ namespace CoreSystem.Forms { op deleteQuestion(@path sectionId: uuid, @path questionId: uuid): { @statusCode statusCode: 204; }; -} + + @summary("Get Filters") + @doc("Get distinct answers of the question for filtering.") + @route("/forms/{formId}/views/{viewId}/questions/{questionId}/filters") + @get + op getFilters(@path formId: uuid, @path viewId: uuid, @path questionId: uuid): { + @statusCode statusCode: 200; + @body filters: QuestionFiltersResponse; + } | { + @statusCode statusCode: 404; + }; +} \ No newline at end of file diff --git a/src/form/response/models.tsp b/src/form/response/models.tsp index f24e6f52..b7b9e0e2 100644 --- a/src/form/response/models.tsp +++ b/src/form/response/models.tsp @@ -176,4 +176,54 @@ namespace CoreSystem.Responses { questionIds: uuid[]; } + @doc("A single answers option selected by the user") + model QueryFilterAnswerItem { + @doc("The raw value used for backend querying and matching.") + matchValue: uuid; + } + + @doc("Filter group for a specific question") + model ResponseQueryFilter { + @doc("The unique identifier of the question.") + questionId: uuid; + + @doc("The list of answers selected for this question.") + answers: QueryFilterAnswerItem[]; + } + + @doc("Request model for filtering form responses") + @example(#{ + formId: "550e8400-e29b-41d4-a716-446655440000", + filters: #[ + #{ + questionId: "a1b2c3d4-1111-2222-3333-444455556666", + answers: #[ + #{ matchValue: "abc-123-def" }, + #{ matchValue: "ghi-456-jkl" } + ] + }, + #{ + questionId: "e5f6g7h8-5555-6666-7777-888899990000", + answers: #[ + #{ matchValue: "陳姝安" } + ] + } + ] + }) + model AnswerFilterRequest { + @doc("The unique identifier of the form.") + formId: uuid; + + @doc("The List of questions selected.") + filters: ResponseQueryFilter[]; + } + + @doc("Response model for filtering form responses") + model AnswerFilterResponse { + @doc("The form unique identifier.") + formId: uuid; + + @doc("The form responses that match the selected answers.") + responses: ExportRow[]; + } } diff --git a/src/form/response/operations.tsp b/src/form/response/operations.tsp index 12deaeb9..cb86e6a9 100644 --- a/src/form/response/operations.tsp +++ b/src/form/response/operations.tsp @@ -114,4 +114,17 @@ namespace CoreSystem.Responses { @statusCode statusCode: 404; @body error: NotFound; }; + + @summary("Filter And Query Form Responses") + @doc("Query and filter form responses based on a list of selected question answers.") + @route("/forms/{formId}/responses/query") + @post + op filterAndQueryFormResponses(@path formId: uuid, @body req: AnswerFilterRequest): { + @statusCode statusCode: 200; + @body response: AnswerFilterResponse; + } | { + @statusCode statusCode: 400; + } | { + @statusCode statueCode: 404; + }; } diff --git a/src/form/view/models.tsp b/src/form/view/models.tsp index c447a12c..b4f61a04 100644 --- a/src/form/view/models.tsp +++ b/src/form/view/models.tsp @@ -1,6 +1,16 @@ using Http; namespace CoreSystem.Views { + + @doc("A single question option available for filtering.") + model Column{ + @doc("The question's unique identifier.") + questionId: uuid; + + @doc("The text displayed to the user in the filter UI.") + questionTitle: string; + } + @doc("Response model for a single form view.") @example(#{ id: "f1a2b3c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c", @@ -9,7 +19,12 @@ namespace CoreSystem.Views { order: 0, createdAt: utcDateTime.fromISO("2025-01-15T10:00:00Z"), updatedAt: utcDateTime.fromISO("2025-02-10T14:30:00Z"), + columns: #[ + #{ questionId: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", questionTitle: "部門" }, + #{ questionId: "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b", questionTitle: "姓名" } + ] }) + model ViewResponse { @doc("The view's unique identifier.") id: uuid; @@ -28,6 +43,9 @@ namespace CoreSystem.Views { @doc("The last updated timestamp of the view.") updatedAt: utcDateTime; + + @doc("The columns of the filters.") + columns: Column[]; } @doc("Request model for updating a view. Only provided fields will be updated.") From 6f2f3445d3f676abb10ce76d5d338cca76a484cb Mon Sep 17 00:00:00 2001 From: shuan Date: Mon, 22 Jun 2026 13:26:10 +0800 Subject: [PATCH 2/6] fix: fix the grammar, naming, and remove formId from AnswerFilterRequest --- src/form/response/models.tsp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/form/response/models.tsp b/src/form/response/models.tsp index b7b9e0e2..8e058e5b 100644 --- a/src/form/response/models.tsp +++ b/src/form/response/models.tsp @@ -176,10 +176,10 @@ namespace CoreSystem.Responses { questionIds: uuid[]; } - @doc("A single answers option selected by the user") + @doc("A single answer option selected by the user") model QueryFilterAnswerItem { @doc("The raw value used for backend querying and matching.") - matchValue: uuid; + option: uuid; } @doc("Filter group for a specific question") @@ -193,27 +193,17 @@ namespace CoreSystem.Responses { @doc("Request model for filtering form responses") @example(#{ - formId: "550e8400-e29b-41d4-a716-446655440000", filters: #[ #{ questionId: "a1b2c3d4-1111-2222-3333-444455556666", answers: #[ - #{ matchValue: "abc-123-def" }, - #{ matchValue: "ghi-456-jkl" } - ] - }, - #{ - questionId: "e5f6g7h8-5555-6666-7777-888899990000", - answers: #[ - #{ matchValue: "陳姝安" } + #{ option: "abc-123-def" }, + #{ option: "ghi-456-jkl" } ] } ] }) model AnswerFilterRequest { - @doc("The unique identifier of the form.") - formId: uuid; - @doc("The List of questions selected.") filters: ResponseQueryFilter[]; } From 07309e09092e3fb056c85c50a23cb622a1032827 Mon Sep 17 00:00:00 2001 From: shuan Date: Tue, 23 Jun 2026 21:14:58 +0800 Subject: [PATCH 3/6] fix: add viewId and questionId to QuestionFiltersResponse --- src/form/question/models.tsp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/form/question/models.tsp b/src/form/question/models.tsp index 8973dbd0..761b002b 100644 --- a/src/form/question/models.tsp +++ b/src/form/question/models.tsp @@ -424,9 +424,15 @@ namespace CoreSystem.Forms { ] }) model QuestionFiltersResponse { + @doc("The unique identifier of the view.") + viewId: uuid; + @doc("The unique identifier of the form.") formId: uuid; + @doc("The unique identifier of the question.") + questionId: uuid; + @doc("The list of available unique answers for this question.") answers: FilterAnswerItem[]; } From ce6cc75807bab7c275d8ca5f6c51f62de53afc91 Mon Sep 17 00:00:00 2001 From: shuan Date: Tue, 23 Jun 2026 21:35:49 +0800 Subject: [PATCH 4/6] fix: fix the example --- src/form/question/models.tsp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/form/question/models.tsp b/src/form/question/models.tsp index 761b002b..f995273c 100644 --- a/src/form/question/models.tsp +++ b/src/form/question/models.tsp @@ -417,7 +417,9 @@ namespace CoreSystem.Forms { @doc("Response model for getting question filters.") @example(#{ + viewId: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", formId: "550e8400-e29b-41d4-a716-446655440000", + questionId: "74723192-bc11-412e-a576-928b76c8c9e0", answers: #[ #{ displayValue: "開發部", matchValue: "abc-123-def", selected: false }, #{ displayValue: "品牌部", matchValue: "ghi-456-jkl", selected: false } From d5c109630e1b6603e4783c520a034ce2d722d1a5 Mon Sep 17 00:00:00 2001 From: shuan Date: Fri, 26 Jun 2026 22:48:39 +0800 Subject: [PATCH 5/6] fix: change API 1 form GET to POST --- src/form/question/operations.tsp | 16 ++++++++++++---- src/form/response/models.tsp | 4 ++-- src/form/response/operations.tsp | 6 ++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/form/question/operations.tsp b/src/form/question/operations.tsp index 81b934e5..7444afab 100644 --- a/src/form/question/operations.tsp +++ b/src/form/question/operations.tsp @@ -1,5 +1,7 @@ using Http; +import "../response/models.tsp"; + @tag("Forms") namespace CoreSystem.Forms { // for form edit - question management @@ -39,13 +41,19 @@ namespace CoreSystem.Forms { }; @summary("Get Filters") - @doc("Get distinct answers of the question for filtering.") + @doc("Filter and query distinct answers of the question for filtering.") @route("/forms/{formId}/views/{viewId}/questions/{questionId}/filters") - @get - op getFilters(@path formId: uuid, @path viewId: uuid, @path questionId: uuid): { + @post + op filterAndQueryAnswers( + @path formId: uuid, + @path viewId: uuid, + @path questionId: uuid, + @body req: CoreSystem.Responses.AnswerFilterRequest + ): { @statusCode statusCode: 200; - @body filters: QuestionFiltersResponse; + @body response: QuestionFiltersResponse; } | { @statusCode statusCode: 404; + @body error: NotFound; }; } \ No newline at end of file diff --git a/src/form/response/models.tsp b/src/form/response/models.tsp index 8e058e5b..e4ca3edf 100644 --- a/src/form/response/models.tsp +++ b/src/form/response/models.tsp @@ -197,8 +197,8 @@ namespace CoreSystem.Responses { #{ questionId: "a1b2c3d4-1111-2222-3333-444455556666", answers: #[ - #{ option: "abc-123-def" }, - #{ option: "ghi-456-jkl" } + #{ option: "9a843aa0-8451-4e3b-b6a0-8c0e994e9040" }, + #{ option: "00000000-0000-0000-0000-000000000001" } ] } ] diff --git a/src/form/response/operations.tsp b/src/form/response/operations.tsp index cb86e6a9..af8ed404 100644 --- a/src/form/response/operations.tsp +++ b/src/form/response/operations.tsp @@ -116,7 +116,7 @@ namespace CoreSystem.Responses { }; @summary("Filter And Query Form Responses") - @doc("Query and filter form responses based on a list of selected question answers.") + @doc("Filter and query form responses based on a list of selected question answers.") @route("/forms/{formId}/responses/query") @post op filterAndQueryFormResponses(@path formId: uuid, @body req: AnswerFilterRequest): { @@ -124,7 +124,9 @@ namespace CoreSystem.Responses { @body response: AnswerFilterResponse; } | { @statusCode statusCode: 400; + @body error: BadRequest; } | { - @statusCode statueCode: 404; + @statusCode statusCode: 404; + @body error: NotFound; }; } From dc7004866b10d9fcf6e2326284f2118cf6bf2960 Mon Sep 17 00:00:00 2001 From: shuan Date: Fri, 26 Jun 2026 22:52:04 +0800 Subject: [PATCH 6/6] fix: let import be prior to namespace --- src/form/question/operations.tsp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/form/question/operations.tsp b/src/form/question/operations.tsp index 7444afab..77c39b61 100644 --- a/src/form/question/operations.tsp +++ b/src/form/question/operations.tsp @@ -1,7 +1,7 @@ -using Http; - import "../response/models.tsp"; +using Http; + @tag("Forms") namespace CoreSystem.Forms { // for form edit - question management