Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Issue #3694] Add Sorting To Saved Opportunity #3846

Merged
merged 26 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8b3e867
[Issue #3579] Update opportunity CSV generation
chouinar Jan 29, 2025
8fc7c30
Add unit tests
chouinar Jan 29, 2025
f9b8c0a
[Issue #3577] Modify sorting to support sorting by multiple fields
chouinar Jan 30, 2025
603084a
Merge branch 'main' into chouinar/3577-multisort
chouinar Jan 30, 2025
8c972eb
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Jan 30, 2025
0100922
Merge branch 'main' into chouinar/3577-multisort
chouinar Feb 6, 2025
c68d065
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Feb 6, 2025
a37a9e8
update schema
babebe Feb 7, 2025
6318297
cleanup
babebe Feb 10, 2025
6d19fb7
merge main
babebe Feb 10, 2025
2c85741
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov into…
babebe Feb 10, 2025
5519a09
wip
babebe Feb 10, 2025
04d33a4
use util func
babebe Feb 10, 2025
2e3e87d
update func and test
babebe Feb 11, 2025
0123b28
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov into…
babebe Feb 11, 2025
5010dd3
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Feb 11, 2025
3409fd2
merge main
babebe Feb 11, 2025
f86ecbf
Merge branch '3694/saved-opp-sorting' of https://github.com/HHS/simpl…
babebe Feb 11, 2025
bf126f0
add test case
babebe Feb 12, 2025
d78087b
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov into…
babebe Feb 12, 2025
18f15a6
fix lint
babebe Feb 12, 2025
f5d233e
use nulls_last(col)
babebe Feb 12, 2025
f5afbda
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov into…
babebe Feb 12, 2025
4f95948
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov into…
babebe Feb 13, 2025
7c248a7
merge
babebe Feb 19, 2025
0d26f21
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Feb 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 148 additions & 75 deletions api/openapi.generated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ paths:
security:
- ApiJwtAuth: []
/v1/users/{user_id}/saved-opportunities:
get:
post:
parameters:
- in: path
name: user_id
Expand All @@ -507,8 +507,14 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UserSavedOpportunitiesResponse'
$ref: '#/components/schemas/UserSaveOpportunityResponse'
description: Successful response
'422':
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Validation error
'401':
content:
application/json:
Expand All @@ -523,9 +529,15 @@ paths:
description: Not found
tags:
- User v1
summary: User Get Saved Opportunities
summary: User Save Opportunity
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserSaveOpportunityRequest'
security:
- ApiJwtAuth: []
/v1/users/{user_id}/saved-searches/list:
post:
parameters:
- in: path
Expand All @@ -538,7 +550,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UserSaveOpportunityResponse'
$ref: '#/components/schemas/UserSavedSearchesResponse'
description: Successful response
'422':
content:
Expand All @@ -560,15 +572,15 @@ paths:
description: Not found
tags:
- User v1
summary: User Save Opportunity
summary: User Get Saved Searches
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserSaveOpportunityRequest'
$ref: '#/components/schemas/UserSavedSearchesRequest'
security:
- ApiJwtAuth: []
/v1/users/{user_id}/saved-searches/list:
/v1/users/{user_id}/saved-opportunities/list:
post:
parameters:
- in: path
Expand All @@ -581,7 +593,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UserSavedSearchesResponse'
$ref: '#/components/schemas/UserSavedOpportunitiesResponse'
description: Successful response
'422':
content:
Expand All @@ -603,12 +615,12 @@ paths:
description: Not found
tags:
- User v1
summary: User Get Saved Searches
summary: User Get Saved Opportunities
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserSavedSearchesRequest'
$ref: '#/components/schemas/UserSavedOpportunitiesRequest'
security:
- ApiJwtAuth: []
/v1/users/{user_id}/saved-searches/{saved_search_id}:
Expand Down Expand Up @@ -2176,71 +2188,6 @@ components:
type: integer
description: The HTTP status code
example: 200
SavedOpportunitySummaryV1:
type: object
properties:
post_date:
type: string
format: date
description: The date the opportunity was posted
example: '2024-01-01'
close_date:
type: string
format: date
description: The date the opportunity will close
example: '2024-01-01'
is_forecast:
type: boolean
description: Whether the opportunity is forecasted
example: false
SavedOpportunityResponseV1:
type: object
properties:
opportunity_id:
type: integer
description: The ID of the saved opportunity
example: 1234
opportunity_title:
type:
- string
- 'null'
description: The title of the opportunity
example: my title
opportunity_status:
description: The current status of the opportunity
example: !!python/object/apply:src.constants.lookup_constants.OpportunityStatus
- posted
enum:
- forecasted
- posted
- closed
- archived
type:
- string
summary:
type:
- object
allOf:
- $ref: '#/components/schemas/SavedOpportunitySummaryV1'
UserSavedOpportunitiesResponse:
type: object
properties:
message:
type: string
description: The message to return
example: Success
data:
type: array
description: List of saved opportunities
items:
type:
- object
allOf:
- $ref: '#/components/schemas/SavedOpportunityResponseV1'
status_code:
type: integer
description: The HTTP status code
example: 200
UserSaveOpportunityRequest:
type: object
properties:
Expand Down Expand Up @@ -2363,6 +2310,132 @@ components:
type: integer
description: The HTTP status code
example: 200
SortOrderUserGetSavedOpportunityPaginationV1:
type: object
properties:
order_by:
type: string
enum:
- created_at
- updated_at
- opportunity_title
- close_date
description: The field to sort the response by
sort_direction:
description: Whether to sort the response ascending or descending
enum:
- ascending
- descending
type:
- string
required:
- order_by
- sort_direction
UserGetSavedOpportunityPaginationV1:
type: object
properties:
sort_order:
type: array
default:
- order_by: created_at
sort_direction: descending
minItems: 1
maxItems: 5
description: The list of sorting rules
items:
type:
- object
allOf:
- $ref: '#/components/schemas/SortOrderUserGetSavedOpportunityPaginationV1'
page_size:
type: integer
minimum: 1
maximum: 5000
description: The size of the page to fetch
example: 25
page_offset:
type: integer
minimum: 1
description: The page number to fetch, starts counting from 1
example: 1
required:
- page_offset
- page_size
UserSavedOpportunitiesRequest:
type: object
properties:
pagination:
type:
- object
allOf:
- $ref: '#/components/schemas/UserGetSavedOpportunityPaginationV1'
required:
- pagination
SavedOpportunitySummaryV1:
type: object
properties:
post_date:
type: string
format: date
description: The date the opportunity was posted
example: '2024-01-01'
close_date:
type: string
format: date
description: The date the opportunity will close
example: '2024-01-01'
is_forecast:
type: boolean
description: Whether the opportunity is forecasted
example: false
SavedOpportunityResponseV1:
type: object
properties:
opportunity_id:
type: integer
description: The ID of the saved opportunity
example: 1234
opportunity_title:
type:
- string
- 'null'
description: The title of the opportunity
example: my title
opportunity_status:
description: The current status of the opportunity
example: !!python/object/apply:src.constants.lookup_constants.OpportunityStatus
- posted
enum:
- forecasted
- posted
- closed
- archived
type:
- string
summary:
type:
- object
allOf:
- $ref: '#/components/schemas/SavedOpportunitySummaryV1'
UserSavedOpportunitiesResponse:
type: object
properties:
message:
type: string
description: The message to return
example: Success
data:
type: array
description: List of saved opportunities
items:
type:
- object
allOf:
- $ref: '#/components/schemas/SavedOpportunityResponseV1'
status_code:
type: integer
description: The HTTP status code
example: 200
UserUpdateSavedSearchRequest:
type: object
properties:
Expand Down
18 changes: 13 additions & 5 deletions api/src/api/users/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
UserDeleteSavedOpportunityResponseSchema,
UserDeleteSavedSearchResponseSchema,
UserGetResponseSchema,
UserSavedOpportunitiesRequestSchema,
UserSavedOpportunitiesResponseSchema,
UserSavedSearchesRequestSchema,
UserSavedSearchesResponseSchema,
Expand Down Expand Up @@ -225,13 +226,16 @@ def user_delete_saved_opportunity(
return response.ApiResponse(message="Success")


@user_blueprint.get("/<uuid:user_id>/saved-opportunities")
@user_blueprint.post("/<uuid:user_id>/saved-opportunities/list")
@user_blueprint.input(UserSavedOpportunitiesRequestSchema, location="json")
@user_blueprint.output(UserSavedOpportunitiesResponseSchema)
@user_blueprint.doc(responses=[200, 401])
@user_blueprint.auth_required(api_jwt_auth)
@flask_db.with_db_session()
def user_get_saved_opportunities(db_session: db.Session, user_id: UUID) -> response.ApiResponse:
logger.info("GET /v1/users/:user_id/saved-opportunities")
def user_get_saved_opportunities(
db_session: db.Session, user_id: UUID, json_data: dict
) -> response.ApiResponse:
logger.info("POST /v1/users/:user_id/saved-opportunities/list")

user_token_session: UserTokenSession = api_jwt_auth.get_user_token_session()

Expand All @@ -240,9 +244,13 @@ def user_get_saved_opportunities(db_session: db.Session, user_id: UUID) -> respo
raise_flask_error(401, "Unauthorized user")

# Get all saved opportunities for the user with their related opportunity data
saved_opportunities = get_saved_opportunities(db_session, user_id)
saved_opportunities, pagination_info = get_saved_opportunities(db_session, user_id, json_data)

return response.ApiResponse(message="Success", data=saved_opportunities)
return response.ApiResponse(
message="Success",
data=saved_opportunities,
pagination_info=pagination_info,
)


@user_blueprint.post("/<uuid:user_id>/saved-searches")
Expand Down
11 changes: 11 additions & 0 deletions api/src/api/users/user_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ class UserDeleteSavedOpportunityResponseSchema(AbstractResponseSchema):
data = fields.MixinField(metadata={"example": None})


class UserSavedOpportunitiesRequestSchema(Schema):
pagination = fields.Nested(
generate_pagination_schema(
"UserGetSavedOpportunityPaginationV1Schema",
["created_at", "updated_at", "opportunity_title", "close_date"],
default_sort_order=[{"order_by": "created_at", "sort_direction": "descending"}],
),
required=True,
)


class UserSavedOpportunitiesResponseSchema(AbstractResponseSchema):
data = fields.List(
fields.Nested(SavedOpportunityResponseV1Schema),
Expand Down
Loading