diff --git a/app/api/openapi/pullreq.go b/app/api/openapi/pullreq.go index 03e00f0f0..959f61d23 100644 --- a/app/api/openapi/pullreq.go +++ b/app/api/openapi/pullreq.go @@ -458,6 +458,20 @@ var queryParameterReviewDecision = openapi3.ParameterOrRef{ }, } +var queryParameterMentionedID = openapi3.ParameterOrRef{ + Parameter: &openapi3.Parameter{ + Name: request.QueryParamMentionedID, + In: openapi3.ParameterInQuery, + Description: ptr.String("Return only pull requests where this user has been mentioned."), + Required: ptr.Bool(false), + Schema: &openapi3.SchemaOrRef{ + Schema: &openapi3.Schema{ + Type: ptrSchemaType(openapi3.SchemaTypeInteger), + }, + }, + }, +} + //nolint:funlen func pullReqOperations(reflector *openapi3.Reflector) { createPullReq := openapi3.Operation{} @@ -483,7 +497,8 @@ func pullReqOperations(reflector *openapi3.Reflector) { queryParameterIncludeDescription, QueryParameterPage, QueryParameterLimit, QueryParameterLabelID, QueryParameterValueID, - queryParameterAuthorID, queryParameterCommenterID, queryParameterReviewerID, queryParameterReviewDecision) + queryParameterAuthorID, queryParameterCommenterID, queryParameterMentionedID, + queryParameterReviewerID, queryParameterReviewDecision) _ = reflector.SetRequest(&listPullReq, new(listPullReqRequest), http.MethodGet) _ = reflector.SetJSONResponse(&listPullReq, new([]types.PullReq), http.StatusOK) _ = reflector.SetJSONResponse(&listPullReq, new(usererror.Error), http.StatusBadRequest) diff --git a/app/api/openapi/space.go b/app/api/openapi/space.go index a22890534..9aa686722 100644 --- a/app/api/openapi/space.go +++ b/app/api/openapi/space.go @@ -612,7 +612,8 @@ func spaceOperations(reflector *openapi3.Reflector) { queryParameterIncludeDescription, queryParameterIncludeSubspaces, QueryParameterLimit, QueryParameterLabelID, QueryParameterValueID, - queryParameterAuthorID, queryParameterCommenterID, queryParameterReviewerID, queryParameterReviewDecision) + queryParameterAuthorID, queryParameterCommenterID, queryParameterMentionedID, + queryParameterReviewerID, queryParameterReviewDecision) _ = reflector.SetRequest(&listPullReq, new(listPullReqRequest), http.MethodGet) _ = reflector.SetJSONResponse(&listPullReq, new([]types.PullReq), http.StatusOK) _ = reflector.SetJSONResponse(&listPullReq, new(usererror.Error), http.StatusBadRequest) diff --git a/app/api/request/pullreq.go b/app/api/request/pullreq.go index 76b954e55..8e3174378 100644 --- a/app/api/request/pullreq.go +++ b/app/api/request/pullreq.go @@ -33,6 +33,7 @@ const ( QueryParamCommenterID = "commenter_id" QueryParamReviewerID = "reviewer_id" QueryParamReviewDecision = "review_decision" + QueryParamMentionedID = "mentioned_id" QueryParamIncludeDescription = "include_description" ) @@ -144,6 +145,11 @@ func ParsePullReqFilter(r *http.Request) (*types.PullReqFilter, error) { return nil, errors.InvalidArgument("Can't use review decisions without providing a reviewer ID") } + mentionedID, err := QueryParamAsPositiveInt64OrDefault(r, QueryParamMentionedID, 0) + if err != nil { + return nil, fmt.Errorf("encountered error parsing mentioned ID filter: %w", err) + } + return &types.PullReqFilter{ Page: ParsePage(r), Size: ParseLimit(r), @@ -161,6 +167,7 @@ func ParsePullReqFilter(r *http.Request) (*types.PullReqFilter, error) { CommenterID: commenterID, ReviewerID: reviewerID, ReviewDecisions: reviewDecisions, + MentionedID: mentionedID, IncludeDescription: includeDescription, CreatedFilter: createdAtFilter, EditedFilter: editedAtFilter, diff --git a/app/store/database/database.go b/app/store/database/database.go new file mode 100644 index 000000000..561a36453 --- /dev/null +++ b/app/store/database/database.go @@ -0,0 +1,20 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package database + +const ( + PostgresDriverName = "postgres" + SqliteDriverName = "sqlite3" +) diff --git a/app/store/database/pullreq.go b/app/store/database/pullreq.go index f64a9375d..58a511fc2 100644 --- a/app/store/database/pullreq.go +++ b/app/store/database/pullreq.go @@ -547,7 +547,7 @@ func (s *PullReqStore) listQuery(opts *types.PullReqFilter) squirrel.SelectBuild columns = pullReqColumns } - if len(opts.LabelID) > 0 || len(opts.ValueID) > 0 || opts.CommenterID > 0 { + if len(opts.LabelID) > 0 || len(opts.ValueID) > 0 || opts.CommenterID > 0 || opts.MentionedID > 0 { stmt = database.Builder.Select("DISTINCT " + columns) } else { stmt = database.Builder.Select(columns) @@ -560,7 +560,8 @@ func (s *PullReqStore) listQuery(opts *types.PullReqFilter) squirrel.SelectBuild return stmt } -func (*PullReqStore) applyFilter(stmt *squirrel.SelectBuilder, opts *types.PullReqFilter) { +//nolint:cyclop +func (s *PullReqStore) applyFilter(stmt *squirrel.SelectBuilder, opts *types.PullReqFilter) { if len(opts.States) == 1 { *stmt = stmt.Where("pullreq_state = ?", opts.States[0]) } else if len(opts.States) > 1 { @@ -626,10 +627,12 @@ func (*PullReqStore) applyFilter(stmt *squirrel.SelectBuilder, opts *types.PullR } if opts.CommenterID > 0 { - *stmt = stmt.InnerJoin("pullreq_activities ON pullreq_activity_pullreq_id = pullreq_id") - *stmt = stmt.Where("pullreq_activity_deleted IS NULL") - *stmt = stmt.Where("(pullreq_activity_kind = 'comment' OR pullreq_activity_kind = 'change-comment')") - *stmt = stmt.Where("pullreq_activity_created_by = ?", opts.CommenterID) + *stmt = stmt.InnerJoin("pullreq_activities act_com ON act_com.pullreq_activity_pullreq_id = pullreq_id") + *stmt = stmt.Where("act_com.pullreq_activity_deleted IS NULL") + *stmt = stmt.Where("(" + + "act_com.pullreq_activity_kind = '" + string(enum.PullReqActivityKindComment) + "' OR " + + "act_com.pullreq_activity_kind = '" + string(enum.PullReqActivityKindChangeComment) + "')") + *stmt = stmt.Where("act_com.pullreq_activity_created_by = ?", opts.CommenterID) } if opts.ReviewerID > 0 { @@ -641,6 +644,25 @@ func (*PullReqStore) applyFilter(stmt *squirrel.SelectBuilder, opts *types.PullR } } + if opts.MentionedID > 0 { + *stmt = stmt.InnerJoin("pullreq_activities act_ment ON act_ment.pullreq_activity_pullreq_id = pullreq_id") + *stmt = stmt.Where("act_ment.pullreq_activity_deleted IS NULL") + *stmt = stmt.Where("(" + + "act_ment.pullreq_activity_kind = '" + string(enum.PullReqActivityKindComment) + "' OR " + + "act_ment.pullreq_activity_kind = '" + string(enum.PullReqActivityKindChangeComment) + "')") + + switch s.db.DriverName() { + case SqliteDriverName: + *stmt = stmt.InnerJoin( + "json_each(json_extract(act_ment.pullreq_activity_metadata, '$.mentions.ids')) as mentions") + *stmt = stmt.Where("mentions.value = ?", opts.MentionedID) + case PostgresDriverName: + *stmt = stmt.Where(fmt.Sprintf( + "act_ment.pullreq_activity_metadata->'mentions'->'ids' @> ('[%d]')::jsonb", + opts.MentionedID)) + } + } + // labels if len(opts.LabelID) == 0 && len(opts.ValueID) == 0 { diff --git a/types/pullreq.go b/types/pullreq.go index 715a97419..95767d175 100644 --- a/types/pullreq.go +++ b/types/pullreq.go @@ -112,6 +112,7 @@ type PullReqFilter struct { CommenterID int64 `json:"commenter_id"` ReviewerID int64 `json:"reviewer_id"` ReviewDecisions []enum.PullReqReviewDecision `json:"review_decisions"` + MentionedID int64 `json:"mentioned_id"` IncludeDescription bool `json:"include_description"` CreatedFilter EditedFilter