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

Enable server-side pagination for update_channels #802

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
86 changes: 79 additions & 7 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 @@ -2507,6 +2528,27 @@ enum UpdateChannelSortField {
NAME
}

":update_channel connection"
type UpdateChannelConnection {
"Total count on all pages"
count: Int

"Page information"
pageInfo: PageInfo!

":update_channel edges"
edges: [UpdateChannelEdge!]
}

":update_channel edge"
type UpdateChannelEdge {
"Cursor"
cursor: String!

":update_channel node"
node: UpdateChannel!
}

input UpdateChannelFilterName {
isNil: Boolean
eq: String
Expand Down Expand Up @@ -2598,12 +2640,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 @@ -2870,7 +2918,19 @@ type RootQueryType {

"A filter to limit the results"
filter: UpdateChannelFilterInput
): [UpdateChannel!]!

"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
): UpdateChannelConnection

"Retrieves the current tenant."
tenantInfo: TenantInfo!
Expand Down Expand Up @@ -2906,7 +2966,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;
54 changes: 35 additions & 19 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 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 { UpdateChannelsTable_PaginationQuery } from "api/__generated__/UpdateChannelsTable_PaginationQuery.graphql";
import type {
UpdateChannelsTable_UpdateChannelFragment$data,
UpdateChannelsTable_UpdateChannelFragment$key,
Expand All @@ -33,18 +34,33 @@ 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 UpdateChannelsTable_UpdateChannelFragment on UpdateChannel
@relay(plural: true) {
id
name
handle
targetGroups {
name
fragment UpdateChannelsTable_UpdateChannelFragment on RootQueryType
@refetchable(queryName: "UpdateChannelsTable_PaginationQuery") {
updateChannels(first: $first, after: $after)
@connection(key: "UpdateChannelsTable_updateChannels") {
edges {
node {
id
name
handle
targetGroups {
edges {
node {
name
}
}
}
}
}
}
}
`;

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

const columnHelper = createColumnHelper<TableRecord>();
const columns = [
Expand Down Expand Up @@ -85,7 +101,7 @@ const columns = [
),
cell: ({ getValue }) => (
<>
{getValue().map((group) => (
{getValue().edges?.map(({ node: group }) => (
<Tag key={group.name} className="me-2">
{group.name}
</Tag>
Expand All @@ -101,14 +117,14 @@ type Props = {
};

const UpdateChannelsTable = ({ className, updateChannelsRef }: Props) => {
const updateChannels = useFragment(
DEVICE_GROUPS_TABLE_FRAGMENT,
updateChannelsRef,
);

return (
<Table className={className} columns={columns} data={updateChannels} />
);
const { data } = usePaginationFragment<
UpdateChannelsTable_PaginationQuery,
UpdateChannelsTable_UpdateChannelFragment$key
>(DEVICE_GROUPS_TABLE_FRAGMENT, updateChannelsRef);

const tableData = data.updateChannels?.edges?.map((edge) => edge.node) ?? [];

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

export default UpdateChannelsTable;
10 changes: 7 additions & 3 deletions frontend/src/forms/CreateUpdateCampaign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@ const UPDATE_CAMPAIGN_OPTIONS_FRAGMENT = graphql`
}
}
updateChannels {
id
name
edges {
node {
id
name
}
}
}
}
`;
Expand Down Expand Up @@ -314,7 +318,7 @@ const CreateBaseImageCollectionForm = ({
defaultMessage: "Select an Update Channel",
})}
</option>
{updateChannels.map((updateChannel) => (
{updateChannels?.edges?.map(({ node: updateChannel }) => (
<option key={updateChannel.id} value={updateChannel.id}>
{updateChannel.name}
</option>
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
Loading