Skip to content

Commit 6269990

Browse files
committed
feat(frontend): add subscriptions for campaigns
- Added subscriptions to DeploymentCampaigns page - Added subscriptions to DeploymentCampaign page - Added subscriptions to UpdateCampaigns page - Added subscriptions to UpdateCampaign page - Added subscriptions to Channels page Signed-off-by: ArnelaL <arnela.lisic@secomind.com>
1 parent 2bda866 commit 6269990

9 files changed

Lines changed: 894 additions & 353 deletions

File tree

frontend/src/api/schema.graphql

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3662,6 +3662,12 @@ type Application implements Node {
36623662
): ReleaseConnection!
36633663
}
36643664

3665+
type system_model_part_number_result {
3666+
created: SystemModelPartNumber
3667+
updated: SystemModelPartNumber
3668+
destroyed: ID
3669+
}
3670+
36653671
enum SystemModelPartNumberSortField {
36663672
ID
36673673
PART_NUMBER
@@ -4004,6 +4010,12 @@ type SystemModel implements Node {
40044010
localizedDescriptions(preferredLanguageTags: [String!]): [LocalizedAttribute!]
40054011
}
40064012

4013+
type hardware_type_part_number_result {
4014+
created: HardwareTypePartNumber
4015+
updated: HardwareTypePartNumber
4016+
destroyed: ID
4017+
}
4018+
40074019
enum HardwareTypePartNumberSortField {
40084020
ID
40094021
PART_NUMBER
@@ -4083,6 +4095,12 @@ type HardwareTypePartNumber {
40834095
hardwareType: HardwareType
40844096
}
40854097

4098+
type hardware_type_result {
4099+
created: HardwareType
4100+
updated: HardwareType
4101+
destroyed: ID
4102+
}
4103+
40864104
"The result of the :delete_hardware_type mutation"
40874105
type DeleteHardwareTypeResult {
40884106
"The record that was successfully deleted"
@@ -5029,6 +5047,12 @@ type Tag {
50295047
name: String!
50305048
}
50315049

5050+
type ota_operation_result {
5051+
created: OtaOperation
5052+
updated: OtaOperation
5053+
destroyed: ID
5054+
}
5055+
50325056
"The result of the :cancel_ota_operation mutation"
50335057
type CancelOtaOperationResult {
50345058
"The successful result of the mutation"
@@ -5275,6 +5299,12 @@ type TenantInfo {
52755299
defaultLocale: String!
52765300
}
52775301

5302+
type channels_result {
5303+
created: Channel
5304+
updated: Channel
5305+
destroyed: ID
5306+
}
5307+
52785308
"The result of the :delete_channel mutation"
52795309
type DeleteChannelResult {
52805310
"The record that was successfully deleted"
@@ -5650,6 +5680,25 @@ type CampaignTarget {
56505680
otaOperation: OtaOperation
56515681
}
56525682

5683+
type campaign_result {
5684+
updated: Campaign
5685+
}
5686+
5687+
type update_campaigns_result {
5688+
created: Campaign
5689+
updated: Campaign
5690+
}
5691+
5692+
type deployment_campaigns_result {
5693+
created: Campaign
5694+
updated: Campaign
5695+
}
5696+
5697+
type campaigns_result {
5698+
created: Campaign
5699+
updated: Campaign
5700+
}
5701+
56535702
"The result of the :resume_campaign mutation"
56545703
type ResumeCampaignResult {
56555704
"The successful result of the mutation"
@@ -6465,6 +6514,36 @@ type RootMutationType {
64656514
}
64666515

64676516
type RootSubscriptionType {
6517+
campaigns(
6518+
"A filter to limit the results"
6519+
filter: CampaignFilterInput
6520+
): campaigns_result
6521+
deploymentCampaigns(
6522+
"A filter to limit the results"
6523+
filter: CampaignFilterInput
6524+
6525+
types: [String!]
6526+
): deployment_campaigns_result
6527+
updateCampaigns(
6528+
"A filter to limit the results"
6529+
filter: CampaignFilterInput
6530+
6531+
types: [String!]
6532+
): update_campaigns_result
6533+
campaign(
6534+
"A filter to limit the results"
6535+
filter: CampaignFilterInput
6536+
6537+
id: ID!
6538+
): campaign_result
6539+
channels(
6540+
"A filter to limit the results"
6541+
filter: ChannelFilterInput
6542+
): channels_result
6543+
otaOperation(
6544+
"A filter to limit the results"
6545+
filter: OtaOperationFilterInput
6546+
): ota_operation_result
64686547
deviceGroup(
64696548
"A filter to limit the results"
64706549
filter: DeviceGroupFilterInput
@@ -6473,6 +6552,18 @@ type RootSubscriptionType {
64736552
"A filter to limit the results"
64746553
filter: DeviceFilterInput
64756554
): device_changed_result
6555+
hardwareType(
6556+
"A filter to limit the results"
6557+
filter: HardwareTypeFilterInput
6558+
): hardware_type_result
6559+
hardwareTypePartNumber(
6560+
"A filter to limit the results"
6561+
filter: HardwareTypePartNumberFilterInput
6562+
): hardware_type_part_number_result
6563+
systemModelPartNumber(
6564+
"A filter to limit the results"
6565+
filter: SystemModelPartNumberFilterInput
6566+
): system_model_part_number_result
64766567
baseImage(
64776568
"A filter to limit the results"
64786569
filter: BaseImageFilterInput

frontend/src/components/DeploymentTargetsTable.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const CAMPAIGN_TARGETS_TABLE_FRAGMENT = graphql`
4545
id
4646
name
4747
}
48+
status
4849
retryCount
4950
latestAttempt
5051
completionTimestamp

frontend/src/pages/Channels.tsx

Lines changed: 157 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import { ErrorBoundary } from "react-error-boundary";
2222
import { FormattedMessage } from "react-intl";
2323
import type { PreloadedQuery } from "react-relay/hooks";
2424
import {
25+
ConnectionHandler,
2526
graphql,
2627
usePaginationFragment,
2728
usePreloadedQuery,
2829
useQueryLoader,
30+
useSubscription,
2931
} from "react-relay/hooks";
3032

3133
import { Channels_ChannelsFragment$key } from "@/api/__generated__/Channels_ChannelsFragment.graphql";
@@ -67,6 +69,44 @@ const CHANNELS_FRAGMENT = graphql`
6769
}
6870
`;
6971

72+
const CHANNEL_UPDATED_SUBSCRIPTION = graphql`
73+
subscription Channels_channel_updated_Subscription {
74+
channels {
75+
updated {
76+
id
77+
name
78+
handle
79+
targetGroups {
80+
edges {
81+
node {
82+
name
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}
89+
`;
90+
91+
const CHANNEL_CREATED_SUBSCRIPTION = graphql`
92+
subscription Channels_channel_created_Subscription {
93+
channels {
94+
created {
95+
id
96+
name
97+
handle
98+
targetGroups {
99+
edges {
100+
node {
101+
name
102+
}
103+
}
104+
}
105+
}
106+
}
107+
}
108+
`;
109+
70110
interface ChannelsLayoutContainerProps {
71111
channelsData: Channels_getChannels_Query["response"];
72112
searchText: string | null;
@@ -81,46 +121,129 @@ const ChannelsLayoutContainer = ({
81121
Channels_ChannelsFragment$key
82122
>(CHANNELS_FRAGMENT, channelsData);
83123

84-
const debounceRefetch = useMemo(
85-
() =>
86-
_.debounce((text: string) => {
87-
if (text === "") {
88-
refetch(
89-
{
90-
first: RECORDS_TO_LOAD_FIRST,
124+
const normalizedSearchText = useMemo(
125+
() => (searchText ?? "").trim(),
126+
[searchText],
127+
);
128+
129+
const connectionFilter = useMemo(() => {
130+
if (!normalizedSearchText) return {};
131+
132+
return {
133+
or: [
134+
{ name: { ilike: `%${normalizedSearchText}%` } },
135+
{ handle: { ilike: `%${normalizedSearchText}%` } },
136+
{
137+
targetGroups: {
138+
name: {
139+
ilike: `%${normalizedSearchText}%`,
91140
},
92-
{ fetchPolicy: "network-only" },
141+
},
142+
},
143+
],
144+
};
145+
}, [normalizedSearchText]);
146+
147+
useSubscription(
148+
useMemo(
149+
() => ({
150+
subscription: CHANNEL_UPDATED_SUBSCRIPTION,
151+
variables: {},
152+
}),
153+
[],
154+
),
155+
);
156+
157+
useSubscription(
158+
useMemo(
159+
() => ({
160+
subscription: CHANNEL_CREATED_SUBSCRIPTION,
161+
variables: {},
162+
updater: (store) => {
163+
const channelRoot = store.getRootField("channels");
164+
const newChannel = channelRoot?.getLinkedRecord("created");
165+
if (!newChannel) return;
166+
167+
if (normalizedSearchText !== "") {
168+
const search = normalizedSearchText.toLowerCase();
169+
170+
const name = String(
171+
newChannel.getValue("name") ?? "",
172+
).toLowerCase();
173+
174+
const handle = String(
175+
newChannel.getValue("handle") ?? "",
176+
).toLowerCase();
177+
178+
const targetGroupsConnection =
179+
newChannel.getLinkedRecord("targetGroups");
180+
181+
const targetGroupEdges =
182+
targetGroupsConnection?.getLinkedRecords("edges") ?? [];
183+
184+
const matchesTargetGroup = targetGroupEdges.some((edge) => {
185+
const node = edge?.getLinkedRecord("node");
186+
const groupName = String(
187+
node?.getValue("name") ?? "",
188+
).toLowerCase();
189+
190+
return groupName.includes(search);
191+
});
192+
193+
const matchesText =
194+
name.includes(search) ||
195+
handle.includes(search) ||
196+
matchesTargetGroup;
197+
198+
if (!matchesText) {
199+
return;
200+
}
201+
}
202+
203+
const connection = ConnectionHandler.getConnection(
204+
store.getRoot(),
205+
"Channels_channels",
206+
{ filter: connectionFilter },
93207
);
94-
} else {
95-
refetch(
96-
{
97-
first: RECORDS_TO_LOAD_FIRST,
98-
filter: {
99-
or: [
100-
{ name: { ilike: `%${text}%` } },
101-
{ handle: { ilike: `%${text}%` } },
102-
{
103-
targetGroups: {
104-
name: {
105-
ilike: `%${text}%`,
106-
},
107-
},
108-
},
109-
],
110-
},
111-
},
112-
{ fetchPolicy: "network-only" },
208+
209+
if (!connection) return;
210+
const newChannelId = newChannel.getDataID();
211+
212+
const edges = connection.getLinkedRecords("edges") ?? [];
213+
const alreadyPresent = edges.some(
214+
(edge) =>
215+
edge.getLinkedRecord("node")?.getDataID() === newChannelId,
113216
);
114-
}
115-
}, 500),
116-
[refetch],
217+
if (alreadyPresent) return;
218+
219+
const edge = ConnectionHandler.createEdge(
220+
store,
221+
connection,
222+
newChannel,
223+
"ChannelEdge",
224+
);
225+
226+
ConnectionHandler.insertEdgeAfter(connection, edge);
227+
},
228+
}),
229+
[connectionFilter, normalizedSearchText],
230+
),
117231
);
118232

119233
useEffect(() => {
120-
if (searchText !== null) {
121-
debounceRefetch(searchText);
122-
}
123-
}, [debounceRefetch, searchText]);
234+
const handler = _.debounce(() => {
235+
refetch(
236+
{ first: RECORDS_TO_LOAD_FIRST, filter: connectionFilter },
237+
{ fetchPolicy: "network-only" },
238+
);
239+
}, 500);
240+
241+
handler();
242+
243+
return () => {
244+
handler.cancel();
245+
};
246+
}, [connectionFilter, refetch]);
124247

125248
const loadNextChannels = useCallback(() => {
126249
if (hasNext && !isLoadingNext) {

0 commit comments

Comments
 (0)