Skip to content

Commit 1f72f2a

Browse files
Refine composition hints for progressive @override (#2922)
Composition hints w.r.t. `@override` usage don't _quite_ apply now that fields could be in a "in migration" state when progressive `@override` is in use. This change introduces a different hint for progressive `@override` usage which advises completing the migration before suggesting the removal of the original field.
1 parent 33b937b commit 1f72f2a

File tree

4 files changed

+101
-14
lines changed

4 files changed

+101
-14
lines changed

.changeset/yellow-horses-protect.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/composition": patch
3+
---
4+
5+
Introduce a new composition hint pertaining specifically to progressive `@override` usage (when a `label` argument is present).

composition-js/src/__tests__/hints.test.ts

+58
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,64 @@ describe('hint tests related to the @override directive', () => {
900900
'T.id'
901901
);
902902
});
903+
904+
it('hint when progressive @override migration is in progress', () => {
905+
const subgraph1 = gql`
906+
type Query {
907+
a: Int
908+
}
909+
910+
type T @key(fields: "id"){
911+
id: Int
912+
f: Int @override(from: "Subgraph2", label: "percent(1)")
913+
}
914+
`;
915+
916+
const subgraph2 = gql`
917+
type T @key(fields: "id"){
918+
id: Int
919+
f: Int
920+
}
921+
`;
922+
const result = mergeDocuments(subgraph1, subgraph2);
923+
924+
// We don't want to see the related hint for non-progressive overrides that
925+
// suggest removing the original field.
926+
expect(result.hints).toHaveLength(1);
927+
expect(result).toRaiseHint(
928+
HINTS.OVERRIDE_MIGRATION_IN_PROGRESS,
929+
`Field "T.f" is currently being migrated with progressive @override. Once the migration is complete, remove the field from subgraph "Subgraph2".`,
930+
'T.f',
931+
);
932+
});
933+
934+
it('hint when progressive @override migration is in progress (for a referenced field)', () => {
935+
const subgraph1 = gql`
936+
type Query {
937+
a: Int
938+
}
939+
940+
type T @key(fields: "id"){
941+
id: Int @override(from: "Subgraph2", label: "percent(1)")
942+
}
943+
`;
944+
945+
const subgraph2 = gql`
946+
type T @key(fields: "id"){
947+
id: Int
948+
}
949+
`;
950+
const result = mergeDocuments(subgraph1, subgraph2);
951+
952+
// We don't want to see the related hint for non-progressive overrides that
953+
// suggest removing the original field.
954+
expect(result.hints).toHaveLength(1);
955+
expect(result).toRaiseHint(
956+
HINTS.OVERRIDE_MIGRATION_IN_PROGRESS,
957+
`Field "T.id" on subgraph "Subgraph2" is currently being migrated via progressive @override. It is still used in some federation directive(s) (@key, @requires, and/or @provides) and/or to satisfy interface constraint(s). Once the migration is complete, consider marking it @external explicitly or removing it along with its references.`,
958+
'T.id'
959+
);
960+
});
903961
});
904962

905963
describe('on non-repeatable directives used with incompatible arguments', () => {

composition-js/src/hints.ts

+7
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ const OVERRIDE_DIRECTIVE_CAN_BE_REMOVED = makeCodeDefinition({
163163
description: 'Field with @override directive no longer exists in source subgraph, the directive can be safely removed',
164164
});
165165

166+
const OVERRIDE_MIGRATION_IN_PROGRESS = makeCodeDefinition({
167+
code: 'OVERRIDE_MIGRATION_IN_PROGRESS',
168+
level: HintLevel.INFO,
169+
description: 'Field is currently being migrated with progressive @override. Once the migration is complete, remove the field from the original subgraph.',
170+
});
171+
166172
const UNUSED_ENUM_TYPE = makeCodeDefinition({
167173
code: 'UNUSED_ENUM_TYPE',
168174
level: HintLevel.DEBUG,
@@ -228,6 +234,7 @@ export const HINTS = {
228234
FROM_SUBGRAPH_DOES_NOT_EXIST,
229235
OVERRIDDEN_FIELD_CAN_BE_REMOVED,
230236
OVERRIDE_DIRECTIVE_CAN_BE_REMOVED,
237+
OVERRIDE_MIGRATION_IN_PROGRESS,
231238
UNUSED_ENUM_TYPE,
232239
INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS,
233240
MERGED_NON_REPEATABLE_DIRECTIVE_ARGUMENTS,

composition-js/src/merging/merge.ts

+31-14
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,8 @@ class Merger {
13041304
// if the field being overridden is used, then we need to add an @external directive
13051305
assert(fromField, 'fromField should not be undefined');
13061306
const overriddenSubgraphASTNode = fromField.sourceAST ? addSubgraphToASTNode(fromField.sourceAST, sourceSubgraphName) : undefined;
1307+
const overrideLabel = overrideDirective.arguments().label;
1308+
const overriddenFieldIsReferenced = !!this.metadata(fromIdx).isFieldUsed(fromField);
13071309
if (this.isExternal(fromIdx, fromField)) {
13081310
// The from field is explicitly marked external by the user (which means it is "used" and cannot be completely
13091311
// removed) so the @override can be removed.
@@ -1313,26 +1315,30 @@ class Merger {
13131315
dest,
13141316
overridingSubgraphASTNode,
13151317
));
1316-
} else if (this.metadata(fromIdx).isFieldUsed(fromField)) {
1318+
} else if (overriddenFieldIsReferenced) {
13171319
result.setUsedOverridden(fromIdx);
1318-
this.hints.push(new CompositionHint(
1319-
HINTS.OVERRIDDEN_FIELD_CAN_BE_REMOVED,
1320-
`Field "${dest.coordinate}" on subgraph "${sourceSubgraphName}" is overridden. It is still used in some federation directive(s) (@key, @requires, and/or @provides) and/or to satisfy interface constraint(s), but consider marking it @external explicitly or removing it along with its references.`,
1321-
dest,
1322-
overriddenSubgraphASTNode,
1323-
));
1320+
if (!overrideLabel) {
1321+
this.hints.push(new CompositionHint(
1322+
HINTS.OVERRIDDEN_FIELD_CAN_BE_REMOVED,
1323+
`Field "${dest.coordinate}" on subgraph "${sourceSubgraphName}" is overridden. It is still used in some federation directive(s) (@key, @requires, and/or @provides) and/or to satisfy interface constraint(s), but consider marking it @external explicitly or removing it along with its references.`,
1324+
dest,
1325+
overriddenSubgraphASTNode,
1326+
)
1327+
);
1328+
}
13241329
} else {
13251330
result.setUnusedOverridden(fromIdx);
1326-
this.hints.push(new CompositionHint(
1327-
HINTS.OVERRIDDEN_FIELD_CAN_BE_REMOVED,
1328-
`Field "${dest.coordinate}" on subgraph "${sourceSubgraphName}" is overridden. Consider removing it.`,
1329-
dest,
1330-
overriddenSubgraphASTNode,
1331-
));
1331+
if (!overrideLabel) {
1332+
this.hints.push(new CompositionHint(
1333+
HINTS.OVERRIDDEN_FIELD_CAN_BE_REMOVED,
1334+
`Field "${dest.coordinate}" on subgraph "${sourceSubgraphName}" is overridden. Consider removing it.`,
1335+
dest,
1336+
overriddenSubgraphASTNode,
1337+
));
1338+
}
13321339
}
13331340

13341341
// capture an override label if it exists
1335-
const overrideLabel = overrideDirective.arguments().label;
13361342
if (overrideLabel) {
13371343
const labelRegex = /^[a-zA-Z][a-zA-Z0-9_\-:./]*$/;
13381344
// Enforce that the label matches the following pattern: percent(x)
@@ -1358,6 +1364,17 @@ class Merger {
13581364
{ nodes: overridingSubgraphASTNode }
13591365
));
13601366
}
1367+
1368+
const message = overriddenFieldIsReferenced
1369+
? `Field "${dest.coordinate}" on subgraph "${sourceSubgraphName}" is currently being migrated via progressive @override. It is still used in some federation directive(s) (@key, @requires, and/or @provides) and/or to satisfy interface constraint(s). Once the migration is complete, consider marking it @external explicitly or removing it along with its references.`
1370+
: `Field "${dest.coordinate}" is currently being migrated with progressive @override. Once the migration is complete, remove the field from subgraph "${sourceSubgraphName}".`;
1371+
1372+
this.hints.push(new CompositionHint(
1373+
HINTS.OVERRIDE_MIGRATION_IN_PROGRESS,
1374+
message,
1375+
dest,
1376+
overriddenSubgraphASTNode,
1377+
));
13611378
}
13621379
}
13631380
}

0 commit comments

Comments
 (0)