Skip to content

Commit c58e231

Browse files
authored
Merge branch 'main' into rkeyser/reinvite-flow
2 parents ed142cb + 3ab6dda commit c58e231

File tree

21 files changed

+818
-55
lines changed

21 files changed

+818
-55
lines changed

.github/workflows/backend_checks.yml

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,49 @@ jobs:
193193
- name: Run Celery worker startup check
194194
run: nox -s check_worker_startup
195195

196+
Migration-Checks:
197+
needs: [Check-Backend-Changes, Check-Container-Startup]
198+
if: needs.Check-Backend-Changes.outputs.has_backend_changes == 'true'
199+
strategy:
200+
matrix:
201+
test_selection:
202+
- "check_migrations"
203+
- "check_migration_downgrade"
204+
205+
runs-on: ubuntu-latest
206+
timeout-minutes: 15
207+
continue-on-error: false
208+
steps:
209+
- name: Download container
210+
uses: actions/download-artifact@v4
211+
with:
212+
name: python-${{ env.DEFAULT_PYTHON_VERSION }}
213+
path: /tmp/
214+
215+
- name: Load image
216+
run: docker load --input /tmp/python-${{ env.DEFAULT_PYTHON_VERSION }}.tar
217+
218+
- name: Checkout
219+
uses: actions/checkout@v4
220+
221+
- name: Set Up Python
222+
uses: actions/setup-python@v5
223+
with:
224+
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
225+
cache: "pip"
226+
227+
- name: Install Nox
228+
run: pip install nox>=2022
229+
230+
- name: Login to Docker Hub
231+
uses: docker/login-action@v3
232+
with:
233+
username: ${{ env.DOCKER_USER }}
234+
password: ${{ env.DOCKER_RO_TOKEN }}
235+
236+
- name: Run migration test
237+
run: nox -s "${{ matrix.test_selection }}"
238+
196239
Misc-Tests:
197240
needs: [Check-Backend-Changes, Check-Container-Startup]
198241
if: needs.Check-Backend-Changes.outputs.has_backend_changes == 'true'
@@ -203,7 +246,6 @@ jobs:
203246
- "fides_db_scan"
204247
- "docs_check"
205248
- "minimal_config_startup"
206-
- "check_migrations"
207249

208250
runs-on: ubuntu-latest
209251
timeout-minutes: 15
@@ -523,6 +565,7 @@ jobs:
523565
- Build
524566
- Performance-Checks
525567
- Check-Container-Startup
568+
- Migration-Checks
526569
- Misc-Tests
527570
- Safe-Tests
528571
# Unsafe tests are optional, so we don't include them in the summary
@@ -533,13 +576,15 @@ jobs:
533576
echo "Build: ${{ needs.Build.result }}"
534577
echo "Performance-Checks: ${{ needs.Performance-Checks.result }}"
535578
echo "Check-Container-Startup: ${{ needs.Check-Container-Startup.result }}"
579+
echo "Migration-Checks: ${{ needs.Migration-Checks.result }}"
536580
echo "Misc-Tests: ${{ needs.Misc-Tests.result }}"
537581
echo "Safe-Tests: ${{ needs.Safe-Tests.result }}"
538582
539583
# Fail only if jobs failed (not if skipped)
540584
if [ "${{ needs.Collect-Tests.result }}" == "failure" ] || \
541585
[ "${{ needs.Build.result }}" == "failure" ] || \
542586
[ "${{ needs.Check-Container-Startup.result }}" == "failure" ] || \
587+
[ "${{ needs.Migration-Checks.result }}" == "failure" ] || \
543588
[ "${{ needs.Misc-Tests.result }}" == "failure" ] || \
544589
[ "${{ needs.Safe-Tests.result }}" == "failure" ]; then
545590
echo "❌ One or more required jobs failed"
@@ -550,6 +595,7 @@ jobs:
550595
if [ "${{ needs.Collect-Tests.result }}" == "cancelled" ] || \
551596
[ "${{ needs.Build.result }}" == "cancelled" ] || \
552597
[ "${{ needs.Check-Container-Startup.result }}" == "cancelled" ] || \
598+
[ "${{ needs.Migration-Checks.result }}" == "cancelled" ] || \
553599
[ "${{ needs.Misc-Tests.result }}" == "cancelled" ] || \
554600
[ "${{ needs.Safe-Tests.result }}" == "cancelled" ]; then
555601
echo "❌ One or more required jobs were cancelled"

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ Changes can also be flagged with a GitHub label for tracking purposes. The URL o
2828
### Changed
2929
- Updated filter modal in new privacy request screen to store filters as query params in url [#6818](https://github.com/ethyca/fides/pull/6818)
3030
- Improve Privacy Center FidesJS performance and reliability by caching bundles in memory and adding `stale-while-revalidate` and `stale-if-error` cache headers for improved CDN performance and origin failure resilience [#6689](https://github.com/ethyca/fides/pull/6689)
31+
- Limit action center tree expansion to 4 levels (Database/Schema/Table/Field) by treating fields as leaf nodes [#6907](https://github.com/ethyca/fides/pull/6907)
3132

3233
### Developer Experience
34+
- Added keyboard navigation to CustomList component [#6903](https://github.com/ethyca/fides/pull/6903)
3335
- Added explicit label property to feature flags configuration for flexible display names [#6889](https://github.com/ethyca/fides/pull/6889)
36+
- Added Cypress command to override feature flags in tests without UI interaction [#6890](https://github.com/ethyca/fides/pull/6890)
3437

3538
## [2.73.1](https://github.com/ethyca/fides/compare/2.73.0..2.73.1)
3639

clients/admin-ui/cypress/e2e/feature-flags.cy.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ describe("Feature Flags", () => {
2222
cy.wait("@createConfigurationSettings");
2323

2424
cy.get("#flag-webMonitor").as("flag");
25+
cy.get("@flag").should("have.attr", "aria-checked", "true");
2526

2627
// Check UI and localStorage state has updated
2728
cy.get("@flag").click().should("have.attr", "aria-checked", "false");
@@ -51,4 +52,45 @@ describe("Feature Flags", () => {
5152
// Check that both UI and localStorage state remain unchanged
5253
cy.get("@flag").should("have.attr", "aria-checked", "false");
5354
});
55+
56+
it("Override feature flags programmatically via Cypress command", () => {
57+
// Initial Login
58+
stubOpenIdProviders();
59+
stubPlusAuth();
60+
cy.login();
61+
stubPlus(true);
62+
63+
// Set flags BEFORE visiting the page - this is the ideal pattern
64+
cy.overrideFeatureFlag("webMonitor", false);
65+
cy.overrideFeatureFlag("dataCatalog", false);
66+
67+
// Navigate to feature flags
68+
stubFeatureFlags();
69+
cy.visit("/settings/about");
70+
cy.wait("@createConfigurationSettings");
71+
72+
// Verify the flags were set correctly on initial load
73+
cy.get("#flag-webMonitor").should("have.attr", "aria-checked", "false");
74+
cy.get("#flag-dataCatalog").should("have.attr", "aria-checked", "false");
75+
76+
// Can also override after the page loads and it updates automatically
77+
cy.overrideFeatureFlag("webMonitor", true);
78+
cy.get("#flag-webMonitor").should("have.attr", "aria-checked", "true");
79+
});
80+
81+
it("Set flags before visiting any page for feature-specific tests", () => {
82+
stubOpenIdProviders();
83+
stubPlusAuth();
84+
cy.login();
85+
stubPlus(true);
86+
87+
// This pattern is useful when testing features behind flags
88+
// Set the flag before navigating to test the enabled state
89+
cy.overrideFeatureFlag("dataCatalog", false);
90+
91+
// Now visit a page that uses this flag
92+
cy.visit("/data-catalog");
93+
// The feature will be disabled from the start
94+
cy.getByTestId("Data catalog").should("not.exist");
95+
});
5496
});

clients/admin-ui/cypress/support/commands.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,53 @@ Cypress.Commands.add("assumeRole", (role) => {
7878
});
7979
});
8080

81+
Cypress.Commands.add("overrideFeatureFlag", (flagName, value) => {
82+
cy.window().then((win) => {
83+
// Get or initialize the persisted state from localStorage
84+
const storageKey = STORAGE_ROOT_KEY;
85+
const persistedStateStr = win.localStorage.getItem(storageKey);
86+
const persistedState = persistedStateStr
87+
? JSON.parse(persistedStateStr)
88+
: {};
89+
90+
// Parse the features slice (redux-persist double-stringifies nested objects)
91+
const featuresStr = persistedState.features;
92+
const features = featuresStr ? JSON.parse(featuresStr) : { flags: {} };
93+
94+
// Get or create the flag configuration
95+
const currentFlag = features.flags[flagName] || {
96+
development: value,
97+
test: value,
98+
production: value,
99+
};
100+
101+
// Update the flag for the current environment
102+
const env = process.env.NEXT_PUBLIC_APP_ENV || "test";
103+
features.flags[flagName] = {
104+
...currentFlag,
105+
[env]: value,
106+
};
107+
108+
// Save back to localStorage
109+
persistedState.features = JSON.stringify(features);
110+
win.localStorage.setItem(storageKey, JSON.stringify(persistedState));
111+
112+
// If the Redux store is available (app already loaded), also dispatch to it
113+
// eslint-disable-next-line no-underscore-dangle
114+
if ((win as any).__REDUX_STORE__) {
115+
// eslint-disable-next-line no-underscore-dangle
116+
(win as any).__REDUX_STORE__.dispatch({
117+
type: "features/override",
118+
payload: {
119+
flag: flagName,
120+
env,
121+
value,
122+
},
123+
});
124+
}
125+
});
126+
});
127+
81128
// this prevents an infinite loop that occurs sometimes and causes tests to
82129
// fail-- see https://github.com/cypress-io/cypress/issues/20341
83130
Cypress.on("uncaught:exception", (err) => {
@@ -149,6 +196,18 @@ declare global {
149196
* @example removeMultiValue("input-singlefield");
150197
*/
151198
clearSingleValue(selectorId: string): void;
199+
/**
200+
* Override a feature flag value in the Redux store for testing.
201+
* This allows you to toggle feature flags on/off during tests
202+
* without needing to modify the UI.
203+
*
204+
* @example cy.overrideFeatureFlag("webMonitor", false)
205+
* @example cy.overrideFeatureFlag("dataCatalog", true)
206+
*/
207+
overrideFeatureFlag(
208+
flagName: string,
209+
value: boolean | string | number,
210+
): void;
152211
}
153212
}
154213
}

clients/admin-ui/global.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,10 @@ declare module globalThis {
22
// needs to be in global scope of Admin UI for when we import fides-js components which contain fidesDebugger
33
let fidesDebugger: (...args: unknown[]) => void;
44
}
5+
6+
interface Window {
7+
// Cypress is available on window when running in Cypress tests
8+
Cypress?: any;
9+
// Redux store is exposed for Cypress testing
10+
__REDUX_STORE__?: any;
11+
}

clients/admin-ui/jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ module.exports = {
4343
"^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }],
4444
},
4545
transformIgnorePatterns: [
46-
"/node_modules/",
46+
"/node_modules/(?!(react-hotkeys-hook)/)",
4747
"^.+\\.module\\.(css|sass|scss)$",
4848
],
4949
watchPathIgnorePatterns: ["node_modules"],

clients/admin-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"react-dnd-html5-backend": "^16.0.1",
7171
"react-dom": "^18.3.1",
7272
"react-dropzone": "^14.2.3",
73+
"react-hotkeys-hook": "^5.2.1",
7374
"react-redux": "^9.1.2",
7475
"redux-persist": "^6.0.0",
7576
"swagger-ui-react": "^5.19.0",

clients/admin-ui/src/features/data-discovery-and-detection/action-center/fields/FieldActions.const.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,13 @@ export const AVAILABLE_ACTIONS = {
7878
FieldActionType.MUTE,
7979
FieldActionType.APPROVE,
8080
FieldActionType.PROMOTE,
81+
FieldActionType.ASSIGN_CATEGORIES,
82+
],
83+
Approved: [
84+
FieldActionType.MUTE,
85+
FieldActionType.PROMOTE,
86+
FieldActionType.ASSIGN_CATEGORIES,
8187
],
82-
Approved: [FieldActionType.MUTE, FieldActionType.PROMOTE],
8388
Classifying: [],
8489
Confirmed: [],
8590
Ignored: [FieldActionType.UN_MUTE],

clients/admin-ui/src/features/data-discovery-and-detection/action-center/fields/MonitorTree.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ const mapResponseToTreeData = (
6262
? () => <IconComponent className="h-full" />
6363
: undefined,
6464
status: treeNode.update_status,
65-
isLeaf: !treeNode.has_grandchildren,
65+
isLeaf:
66+
treeNode.resource_type === StagedResourceTypeValue.FIELD ||
67+
!treeNode.has_grandchildren,
6668
};
6769
});
6870

clients/admin-ui/src/pages/_app.tsx

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -38,36 +38,46 @@ const SafeHydrate = ({ children }: { children: ReactNode }) => (
3838
</div>
3939
);
4040

41-
const MyApp = ({ Component, pageProps }: AppProps) => (
42-
<SafeHydrate>
43-
<Provider store={store}>
44-
<PersistGate loading={null} persistor={persistor}>
45-
<FidesUIProvider theme={theme} antTheme={defaultAntTheme}>
46-
<DndProvider backend={HTML5Backend}>
47-
<NuqsAdapter>
48-
{Component === Login || Component === LoginWithOIDC ? (
49-
// Only the login page is accessible while logged out. If there is
50-
// a use case for more unprotected routes, Next has a guide for
51-
// per-page layouts:
52-
// https://nextjs.org/docs/basic-features/layouts#per-page-layouts
53-
<Component {...pageProps} />
54-
) : (
55-
<ProtectedRoute>
56-
<CommonSubscriptions />
57-
<Flex width="100%" height="100%" flex={1}>
58-
<MainSideNav />
59-
<Flex direction="column" width="100%">
60-
<Component {...pageProps} />
41+
const MyApp = ({ Component, pageProps }: AppProps) => {
42+
// Expose Redux store to window for Cypress testing
43+
React.useEffect(() => {
44+
if (typeof window !== "undefined" && window.Cypress) {
45+
// eslint-disable-next-line no-underscore-dangle
46+
window.__REDUX_STORE__ = store;
47+
}
48+
}, []);
49+
50+
return (
51+
<SafeHydrate>
52+
<Provider store={store}>
53+
<PersistGate loading={null} persistor={persistor}>
54+
<FidesUIProvider theme={theme} antTheme={defaultAntTheme}>
55+
<DndProvider backend={HTML5Backend}>
56+
<NuqsAdapter>
57+
{Component === Login || Component === LoginWithOIDC ? (
58+
// Only the login page is accessible while logged out. If there is
59+
// a use case for more unprotected routes, Next has a guide for
60+
// per-page layouts:
61+
// https://nextjs.org/docs/basic-features/layouts#per-page-layouts
62+
<Component {...pageProps} />
63+
) : (
64+
<ProtectedRoute>
65+
<CommonSubscriptions />
66+
<Flex width="100%" height="100%" flex={1}>
67+
<MainSideNav />
68+
<Flex direction="column" width="100%">
69+
<Component {...pageProps} />
70+
</Flex>
6171
</Flex>
62-
</Flex>
63-
</ProtectedRoute>
64-
)}
65-
</NuqsAdapter>
66-
</DndProvider>
67-
</FidesUIProvider>
68-
</PersistGate>
69-
</Provider>
70-
</SafeHydrate>
71-
);
72+
</ProtectedRoute>
73+
)}
74+
</NuqsAdapter>
75+
</DndProvider>
76+
</FidesUIProvider>
77+
</PersistGate>
78+
</Provider>
79+
</SafeHydrate>
80+
);
81+
};
7282

7383
export default MyApp;

0 commit comments

Comments
 (0)