Skip to content

LITE-28409: Adding pagination capability #90

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

Merged
merged 1 commit into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 31 additions & 0 deletions connect_ext_ppr/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from fastapi import Query
from fastapi_pagination import LimitOffsetPage, LimitOffsetParams, set_page
from fastapi_pagination.api import resolve_params
from fastapi_pagination.ext.sqlalchemy import paginate


set_page(LimitOffsetPage)


class PaginationParams(LimitOffsetParams):
"""Here we can redefine default size value"""
limit: int = Query(1000, ge=1, le=1000, description="Page size")


def apply_pagination(query, db, params, response):
"""Apply pagination for the query
* Paginate query according to applied parameters (size and page)
* Add pagination headers to the response
Don't filter or remove elements from query after the pagination
"""
paginated = paginate(db, query, params)
resolve_params(params)
init = paginated.offset
# If we are selecting a page that it's out of range, we return the same start and end for on
# header range, copying the same behavior as connect.
end = init
if paginated.items:
end += len(paginated.items) - 1

response.headers['Content-Range'] = f'items {init}-{end}/{paginated.total}'
return paginated.items
39 changes: 31 additions & 8 deletions connect_ext_ppr/webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from connect_ext_ppr.models.ppr import PPRVersion
from connect_ext_ppr.models.task import Task
from connect_ext_ppr.models.replicas import Product
from connect_ext_ppr.pagination import apply_pagination, PaginationParams
from connect_ext_ppr.service import (
add_deployments,
add_new_deployment_request,
Expand Down Expand Up @@ -182,6 +183,8 @@ def add_dep_request(
)
def list_deployment_requests(
self,
pagination_params: PaginationParams = Depends(),
response: Response = None,
client: ConnectClient = Depends(get_installation_client),
db: VerboseBaseSession = Depends(get_db),
installation: dict = Depends(get_installation),
Expand All @@ -203,6 +206,7 @@ def list_deployment_requests(
).filter(
DeploymentRequest.deployment_id.in_(deployments),
)
deployment_requests = apply_pagination(deployment_requests, db, pagination_params, response)

response_list = []
for dr in deployment_requests:
Expand Down Expand Up @@ -272,6 +276,8 @@ def list_deployment_request_tasks(
def list_deployment_request_marketplaces(
self,
depl_req_id: str,
pagination_params: PaginationParams = Depends(),
response: Response = None,
db: VerboseBaseSession = Depends(get_db),
client: ConnectClient = Depends(get_installation_client),
installation: dict = Depends(get_installation),
Expand All @@ -282,6 +288,7 @@ def list_deployment_request_marketplaces(
marketplaces = db.query(MarketplaceConfiguration).options(
selectinload(MarketplaceConfiguration.ppr),
).filter_by(deployment_request=dr.id)
marketplaces = apply_pagination(marketplaces, db, pagination_params, response)

marketplaces_pprs = {m.marketplace: m.ppr for m in marketplaces}
marketplaces_data = get_marketplaces(client, list(marketplaces_pprs.keys()))
Expand Down Expand Up @@ -369,6 +376,8 @@ def get_deployment(
def list_requests_for_deployment(
self,
deployment_id: str,
pagination_params: PaginationParams = Depends(),
response: Response = None,
client: ConnectClient = Depends(get_installation_client),
db: VerboseBaseSession = Depends(get_db),
installation: dict = Depends(get_installation),
Expand All @@ -384,7 +393,8 @@ def list_requests_for_deployment(
.filter_by(deployment_id=deployment_id)
.order_by(desc(DeploymentRequest.id))
)
for dr in qs:

for dr in apply_pagination(qs, db, pagination_params, response):
response_list.append(get_deployment_request_schema(dr, hub))
return response_list

Expand All @@ -396,12 +406,15 @@ def list_requests_for_deployment(
def get_deployments(
self,
deployment_filter: DeploymentFilter = FilterDepends(DeploymentFilter),
pagination_params: PaginationParams = Depends(),
response: Response = None,
client: ConnectClient = Depends(get_installation_client),
db: VerboseBaseSession = Depends(get_db),
installation: dict = Depends(get_installation),
):
deployments = db.query(Deployment).filter_by(account_id=installation['owner']['id'])
deployments = deployment_filter.filter(deployments)
deployments = apply_pagination(deployments, db, pagination_params, response)
listings = get_all_listing_info(client)
vendors = [li['vendor'] for li in listings]
hubs = [hub['hub'] for li in listings for hub in li['contract']['marketplace']['hubs']]
Expand All @@ -422,17 +435,19 @@ def get_deployments(
def get_configurations(
self,
deployment_id: str,
pagination_params: PaginationParams = Depends(),
response: Response = None,
db: VerboseBaseSession = Depends(get_db),
installation: dict = Depends(get_installation),
):
get_deployment_by_id(deployment_id, db, installation)

conf_file_list = (
conf_file_qs = (
db.query(Configuration, File)
.filter_by(deployment=deployment_id)
.join(File, Configuration.file == File.id)
.all()
)
conf_file_list = apply_pagination(conf_file_qs, db, pagination_params, response)
response_list = []
for conf, file in conf_file_list:
response_list.append(
Expand Down Expand Up @@ -569,20 +584,21 @@ def remove_configuration(
def get_pprs(
self,
deployment_id: str,
pagination_params: PaginationParams = Depends(),
response: Response = None,
db: VerboseBaseSession = Depends(get_db),
installation: dict = Depends(get_installation),
):
get_deployment_by_id(deployment_id, db, installation)

ppr_file_conf_list = (
ppr_file_conf_qs = (
db.query(PPRVersion, File, Configuration)
.filter_by(deployment=deployment_id)
.join(File, PPRVersion.file == File.id)
.outerjoin(Configuration, PPRVersion.configuration == Configuration.id)
.order_by(desc(PPRVersion.version))
.all()
)

ppr_file_conf_list = apply_pagination(ppr_file_conf_qs, db, pagination_params, response)
response_list = []
for ppr, file, conf in ppr_file_conf_list:
response_list.append(
Expand Down Expand Up @@ -642,6 +658,8 @@ def add_ppr(
def get_marketplaces_by_deployment(
self,
deployment_id: str,
pagination_params: PaginationParams = Depends(),
response: Response = None,
client: ConnectClient = Depends(get_installation_client),
db: VerboseBaseSession = Depends(get_db),
installation: dict = Depends(get_installation),
Expand All @@ -651,6 +669,7 @@ def get_marketplaces_by_deployment(
mkplc_configs = db.query(MarketplaceConfiguration).options(
selectinload(MarketplaceConfiguration.ppr),
).filter_by(deployment_id=deployment_id, active=True)
mkplc_configs = apply_pagination(mkplc_configs, db, pagination_params, response)

mkplc_ids = [m.marketplace for m in mkplc_configs]

Expand All @@ -668,19 +687,23 @@ def get_marketplaces_by_deployment(
)
def list_products(
self,
pagination_params: PaginationParams = Depends(),
response: Response = None,
db: VerboseBaseSession = Depends(get_db),
installation: dict = Depends(get_installation),
):
products_ids = db.query(Deployment.product_id).filter_by(
account_id=installation['owner']['id'],
).distinct()

response_list = []
products = db.query(Product).filter(Product.id.in_(products_ids)).options(
selectinload(Product.owner),
)
products = apply_pagination(products, db, pagination_params, response)

response_list = []
for product in products:
response_list.append(get_product_schema(product))

return response_list

@router.get(
Expand Down
34 changes: 33 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pandas = "^2.0.3"
openpyxl = "^3.1.2"
PyJWT = "^2.8.0"
fastapi_filter = "^0.6.1"
fastapi-pagination = "^0.12.8"

[tool.poetry.dev-dependencies]
pytest = ">=6.1.2,<8"
Expand Down
32 changes: 32 additions & 0 deletions tests/api/test_configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,38 @@ def test_get_configurations(
assert data['state'] == ConfigurationStateChoices.inactive


@pytest.mark.parametrize(
('pagination', 'expected_amount', 'expected_header'),
(
('limit=10&offset=0', 10, 'items 0-9/12'),
('limit=5&offset=10', 2, 'items 10-11/12'),
('limit=5&offset=21', 0, 'items 21-21/12'),
),
)
def test_get_configurations_with_pagination(
pagination,
expected_amount,
expected_header,
deployment_factory,
file_factory,
configuration_factory,
installation,
api_client,
):
deployment = deployment_factory(account_id=installation['owner']['id'])
for i in range(12):
ppr_file = file_factory(id=f'ML-{i}')
configuration_factory(file=ppr_file.id, deployment=deployment.id)

response = api_client.get(
f'/api/deployments/{deployment.id}/configurations?{pagination}',
installation=installation,
)
assert response.status_code == 200
assert len(response.json()) == expected_amount
assert response.headers['Content-Range'] == expected_header


def test_get_configurations_empty(
deployment,
installation,
Expand Down
40 changes: 40 additions & 0 deletions tests/api/test_deployment_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,46 @@ def test_list_deployments_requests(
assert list(events['created'].keys()) == ['at', 'by']


@pytest.mark.parametrize(
('pagination', 'expected_amount', 'expected_header'),
(
('limit=10&offset=0', 10, 'items 0-9/20'),
('limit=9&offset=18', 2, 'items 18-19/20'),
('limit=5&offset=21', 0, 'items 21-21/20'),
),
)
def test_list_deployments_requests_pagination(
pagination,
expected_amount,
expected_header,
mocker,
deployment_factory,
deployment_request_factory,
installation,
api_client,
):
hub_data = {
'id': 'HB-0000-0000',
'name': 'Another Hub for the best',
}
mocker.patch(
'connect_ext_ppr.webapp.get_hubs',
side_effect=[[hub_data]],
)

for _ in range(20):
dep1 = deployment_factory(account_id=installation['owner']['id'])
deployment_request_factory(deployment=dep1)

response = api_client.get(
f'/api/deployments/requests?{pagination}',
installation=installation,
)
assert response.status_code == 200
assert len(response.json()) == expected_amount
assert response.headers['Content-Range'] == expected_header


def test_get_deployment_request(
mocker,
deployment_factory,
Expand Down
Loading