Skip to content

Commit

Permalink
Enable server-side pagination for device_groups and target_groups
Browse files Browse the repository at this point in the history
Updated queries for `device_groups`, and `target_groups` to support
server-side pagination. This change ensures data fetching is paginated
on the backend while maintaining the current client-side logic.
A future update will optimize the respective tables to fully leverage
pagination, avoiding an immediate rewrite of the client-side
implementation.

Part of #779

Signed-off-by: Omar <[email protected]>
  • Loading branch information
OmarBrbutovic committed Jan 15, 2025
1 parent 66642a9 commit abbddc2
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 104 deletions.
51 changes: 45 additions & 6 deletions frontend/src/api/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1885,6 +1885,27 @@ enum DeviceGroupSortField {
SELECTOR
}

":device_group connection"
type DeviceGroupConnection {
"Total count on all pages"
count: Int

"Page information"
pageInfo: PageInfo!

":device_group edges"
edges: [DeviceGroupEdge!]
}

":device_group edge"
type DeviceGroupEdge {
"Cursor"
cursor: String!

":device_group node"
node: DeviceGroup!
}

input DeviceGroupFilterSelector {
isNil: Boolean
eq: String
Expand Down Expand Up @@ -2598,12 +2619,18 @@ type UpdateChannel implements Node {
"A filter to limit the results"
filter: DeviceGroupFilterInput

"The number of records to return."
limit: Int
"The number of records to return from the beginning. Maximum 250"
first: Int

"The number of records to skip."
offset: Int
): [DeviceGroup!]!
"Show records before the specified keyset."
before: String

"Show records after the specified keyset."
after: String

"The number of records to return to the end. Maximum 250"
last: Int
): DeviceGroupConnection!
}

"The result of the :create_update_campaign mutation"
Expand Down Expand Up @@ -2906,7 +2933,19 @@ type RootQueryType {

"A filter to limit the results"
filter: DeviceGroupFilterInput
): [DeviceGroup!]!

"The number of records to return from the beginning. Maximum 250"
first: Int

"Show records before the specified keyset."
before: String

"Show records after the specified keyset."
after: String

"The number of records to return to the end. Maximum 250"
last: Int
): DeviceGroupConnection

"""
Fetches the forwarder config, if available.
Expand Down
42 changes: 28 additions & 14 deletions frontend/src/components/DeviceGroupsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This file is part of Edgehog.
Copyright 2022-2023 SECO Mind Srl
Copyright 2022-2025 SECO Mind Srl
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -19,8 +19,9 @@
*/

import { FormattedMessage } from "react-intl";
import { graphql, useFragment } from "react-relay/hooks";
import { graphql, usePaginationFragment } from "react-relay/hooks";

import type { DeviceGroupsTable_PaginationQuery } from "api/__generated__/DeviceGroupsTable_PaginationQuery.graphql";
import type {
DeviceGroupsTable_DeviceGroupFragment$data,
DeviceGroupsTable_DeviceGroupFragment$key,
Expand All @@ -32,16 +33,27 @@ import { Link, Route } from "Navigation";
// We use graphql fields below in columns configuration
/* eslint-disable relay/unused-fields */
const DEVICE_GROUPS_TABLE_FRAGMENT = graphql`
fragment DeviceGroupsTable_DeviceGroupFragment on DeviceGroup
@relay(plural: true) {
id
name
handle
selector
fragment DeviceGroupsTable_DeviceGroupFragment on RootQueryType
@refetchable(queryName: "DeviceGroupsTable_PaginationQuery") {
deviceGroups(first: $first, after: $after)
@connection(key: "DeviceGroupsTable_deviceGroups") {
edges {
node {
id
name
handle
selector
}
}
}
}
`;

type TableRecord = DeviceGroupsTable_DeviceGroupFragment$data[0];
type TableRecord = NonNullable<
NonNullable<
DeviceGroupsTable_DeviceGroupFragment$data["deviceGroups"]
>["edges"]
>[number]["node"];

const columnHelper = createColumnHelper<TableRecord>();
const columns = [
Expand Down Expand Up @@ -88,12 +100,14 @@ type Props = {
};

const DeviceGroupsTable = ({ className, deviceGroupsRef }: Props) => {
const deviceGroups = useFragment(
DEVICE_GROUPS_TABLE_FRAGMENT,
deviceGroupsRef,
);
const { data } = usePaginationFragment<
DeviceGroupsTable_PaginationQuery,
DeviceGroupsTable_DeviceGroupFragment$key
>(DEVICE_GROUPS_TABLE_FRAGMENT, deviceGroupsRef);

return <Table className={className} columns={columns} data={deviceGroups} />;
const tableData = data.deviceGroups?.edges?.map((edge) => edge.node) ?? [];

return <Table className={className} columns={columns} data={tableData} />;
};

export default DeviceGroupsTable;
10 changes: 7 additions & 3 deletions frontend/src/components/UpdateChannelsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This file is part of Edgehog.
Copyright 2023 SECO Mind Srl
Copyright 2023-2025 SECO Mind Srl
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,7 +39,11 @@ const DEVICE_GROUPS_TABLE_FRAGMENT = graphql`
name
handle
targetGroups {
name
edges {
node {
name
}
}
}
}
`;
Expand Down Expand Up @@ -85,7 +89,7 @@ const columns = [
),
cell: ({ getValue }) => (
<>
{getValue().map((group) => (
{getValue().edges?.map(({ node: group }) => (
<Tag key={group.name} className="me-2">
{group.name}
</Tag>
Expand Down
41 changes: 24 additions & 17 deletions frontend/src/forms/CreateUpdateChannel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This file is part of Edgehog.
Copyright 2023-2024 SECO Mind Srl
Copyright 2023-2025 SECO Mind Srl
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -23,6 +23,8 @@ import { useForm, Controller } from "react-hook-form";
import { useIntl, FormattedMessage } from "react-intl";
import { yupResolver } from "@hookform/resolvers/yup";

import type { CreateUpdateChannel_OptionsFragment$key } from "api/__generated__/CreateUpdateChannel_OptionsFragment.graphql";

import Button from "components/Button";
import Col from "components/Col";
import Form from "components/Form";
Expand All @@ -32,15 +34,18 @@ import Spinner from "components/Spinner";
import Stack from "components/Stack";
import { updateChannelHandleSchema, yup, messages } from "forms";
import { graphql, useFragment } from "react-relay/hooks";
import type { CreateUpdateChannel_OptionsFragment$key } from "api/__generated__/CreateUpdateChannel_OptionsFragment.graphql";

const CREATE_UPDATE_CHANNEL_OPTIONS_FRAGMENT = graphql`
fragment CreateUpdateChannel_OptionsFragment on RootQueryType {
deviceGroups {
id
name
updateChannel {
name
edges {
node {
id
name
updateChannel {
name
}
}
}
}
}
Expand Down Expand Up @@ -179,18 +184,20 @@ const CreateUpdateChannel = ({

const targetGroupOptions = useMemo(() => {
// move disabled options to the end
return [...targetGroups].sort((group1, group2) => {
const group1Disabled = isTargetGroupUsedByOtherChannel(group1);
const group2Disabled = isTargetGroupUsedByOtherChannel(group2);
return [...(targetGroups?.edges?.map((edge) => edge.node) || [])].sort(
(group1, group2) => {
const group1Disabled = isTargetGroupUsedByOtherChannel(group1);
const group2Disabled = isTargetGroupUsedByOtherChannel(group2);

if (group1Disabled === group2Disabled) {
return 0;
}
if (group1Disabled) {
return 1;
}
return -1;
});
if (group1Disabled === group2Disabled) {
return 0;
}
if (group1Disabled) {
return 1;
}
return -1;
},
);
}, [targetGroups]);

const onFormSubmit = (data: FormData) => onSubmit(transformOutputData(data));
Expand Down
Loading

0 comments on commit abbddc2

Please sign in to comment.