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

Connector dataset dropdown #2162

Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ The types of changes are:

## [Unreleased](https://github.com/ethyca/fides/compare/2.4.0...main)

### Added

* Unified Fides Resources: Added a dataset dropdown selector when configuring a connector to link an existing dataset to the connector configuration. [#2162](https://github.com/ethyca/fides/pull/2162)

### Fixed

* Remove next-auth from privacy center to fix JS console error [#2090](https://github.com/ethyca/fides/pull/2090)
Expand Down
65 changes: 61 additions & 4 deletions clients/admin-ui/cypress/e2e/connectors.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ describe("Connectors", () => {
"/api/v1/connection/postgres_connector/datasetconfig",
{ body: {} }
).as("patchDatasetconfig");
cy.intercept("GET", "/api/v1/dataset", { fixture: "datasets.json" }).as(
"getDatasets"
);
});

it("Should show data store connections and view configuration", () => {
Expand All @@ -46,15 +49,51 @@ describe("Connectors", () => {
cy.getByTestId("input-name").should("have.value", "postgres_connector");
});

it("Should allow saving a dataset configuration", () => {
it("Should allow saving a dataset configuration via dropdown", () => {
cy.visit("/datastore-connection/postgres_connector");
cy.getByTestId("tab-Dataset configuration").click();
cy.wait("@getPostgresConnectorDatasetconfig");
// The monaco yaml editor takes a bit to load. Since this is likely going away,
// just wait for now and remove this once the yaml editor is no longer available

// The yaml editor will start off disabled
cy.getByTestId("save-yaml-btn").should("be.disabled");
// The dataset dropdown selector should have the value of the existing connected dataset
cy.getByTestId("save-dataset-link-btn").should("be.enabled");
cy.getByTestId("dataset-selector").should(
"have.value",
"postgres_example_test_dataset"
);

// Change the linked dataset
cy.getByTestId("dataset-selector").select("demo_users_dataset_2");
cy.getByTestId("save-dataset-link-btn").click();

cy.wait("@patchDatasetconfig").then((interception) => {
expect(interception.request.body).to.eql([
{
fides_key: "postgres_example_test_dataset",
ctl_dataset_fides_key: "demo_users_dataset_2",
},
]);
});
});

it("Should allow saving a dataset configuration via yaml", () => {
cy.visit("/datastore-connection/postgres_connector");
cy.getByTestId("tab-Dataset configuration").click();
cy.wait("@getPostgresConnectorDatasetconfig");

// Unset the linked dataset, which should switch the save button enable-ness
cy.getByTestId("dataset-selector").select("Select");
cy.getByTestId("save-dataset-link-btn").should("be.disabled");
// The monaco yaml editor takes a bit to load
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
cy.getByTestId("save-btn").click();
cy.getByTestId("save-yaml-btn").click();

// Click past the confirmation modal
cy.getByTestId("confirmation-modal");
cy.getByTestId("continue-btn").click();

cy.wait("@upsertDataset").then((interception) => {
expect(interception.request.body.length).to.eql(1);
expect(interception.request.body[0].fides_key).to.eql(
Expand All @@ -70,5 +109,23 @@ describe("Connectors", () => {
]);
});
});

it("Should not show the dataset selector if no datasets exist", () => {
cy.intercept("GET", "/api/v1/dataset", { body: [] }).as("getDatasets");
cy.intercept(
"GET",
"/api/v1/connection/postgres_connector/datasetconfig",
{
body: {
items: [],
},
}
).as("getEmptyPostgresConnectorDatasetconfig");

cy.visit("/datastore-connection/postgres_connector");
cy.getByTestId("tab-Dataset configuration").click();
cy.wait("@getEmptyPostgresConnectorDatasetconfig");
cy.getByTestId("dataset-selector-section").should("not.exist");
});
});
});
15 changes: 15 additions & 0 deletions clients/admin-ui/cypress/fixtures/datasets.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,20 @@
]
}
]
},
{
"fides_key": "postgres_example_test_dataset",
"organization_fides_key": "default_organization",
"tags": null,
"name": "Postgres Example Test Dataset",
"description": "Example of a Postgres dataset containing a variety of related tables like customers, products, addresses, etc.",
"meta": null,
"data_categories": null,
"data_qualifier": "aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified",
"fides_meta": null,
"joint_controller": null,
"retention": "No retention or erasure policy",
"third_country_transfers": null,
"collections": []
}
]
17 changes: 6 additions & 11 deletions clients/admin-ui/src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ import {
connectionTypeApi,
reducer as connectionTypeReducer,
} from "connection-type/index";
import {
datastoreConnectionApi,
reducer as datastoreConnectionReducer,
} from "datastore-connections/index";
import { reducer as datastoreConnectionReducer } from "datastore-connections/index";
import {
privacyRequestApi,
reducer as privacyRequestsReducer,
Expand All @@ -34,6 +31,7 @@ import {
} from "user-management/index";

import { STORAGE_ROOT_KEY } from "~/constants";
import { baseApi } from "~/features/common/api.slice";
import { reducer as configWizardReducer } from "~/features/config-wizard/config-wizard.slice";
import { scannerApi } from "~/features/config-wizard/scanner.slice";
import {
Expand All @@ -48,7 +46,7 @@ import {
dataUseApi,
reducer as dataUseReducer,
} from "~/features/data-use/data-use.slice";
import { datasetApi, reducer as datasetReducer } from "~/features/dataset";
import { reducer as datasetReducer } from "~/features/dataset";
import {
organizationApi,
reducer as organizationReducer,
Expand Down Expand Up @@ -83,12 +81,11 @@ const storage =

const reducer = {
[authApi.reducerPath]: authApi.reducer,
[baseApi.reducerPath]: baseApi.reducer,
[connectionTypeApi.reducerPath]: connectionTypeApi.reducer,
[dataQualifierApi.reducerPath]: dataQualifierApi.reducer,
[dataSubjectsApi.reducerPath]: dataSubjectsApi.reducer,
[dataUseApi.reducerPath]: dataUseApi.reducer,
[datasetApi.reducerPath]: datasetApi.reducer,
[datastoreConnectionApi.reducerPath]: datastoreConnectionApi.reducer,
[organizationApi.reducerPath]: organizationApi.reducer,
[plusApi.reducerPath]: plusApi.reducer,
[privacyRequestApi.reducerPath]: privacyRequestApi.reducer,
Expand Down Expand Up @@ -135,12 +132,11 @@ const persistConfig = {
*/
blacklist: [
authApi.reducerPath,
baseApi.reducerPath,
connectionTypeApi.reducerPath,
dataQualifierApi.reducerPath,
dataSubjectsApi.reducerPath,
dataUseApi.reducerPath,
datasetApi.reducerPath,
datastoreConnectionApi.reducerPath,
organizationApi.reducerPath,
plusApi.reducerPath,
privacyRequestApi.reducerPath,
Expand All @@ -163,12 +159,11 @@ export const makeStore = (preloadedState?: Partial<RootState>) =>
},
}).concat(
authApi.middleware,
baseApi.middleware,
connectionTypeApi.middleware,
dataQualifierApi.middleware,
dataSubjectsApi.middleware,
dataUseApi.middleware,
datasetApi.middleware,
datastoreConnectionApi.middleware,
organizationApi.middleware,
plusApi.middleware,
privacyRequestApi.middleware,
Expand Down
22 changes: 22 additions & 0 deletions clients/admin-ui/src/features/common/api.slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

import type { RootState } from "~/app/store";
import { selectToken } from "~/features/auth";

import { addCommonHeaders } from "./CommonHeaders";

// Uses the code splitting pattern. New endpoints should be injected into this base API
// which itself has an empty endpoint object.
export const baseApi = createApi({
reducerPath: "baseApi",
baseQuery: fetchBaseQuery({
baseUrl: process.env.NEXT_PUBLIC_FIDESCTL_API,
prepareHeaders: (headers, { getState }) => {
const token: string | null = selectToken(getState() as RootState);
addCommonHeaders(headers, token);
return headers;
},
}),
tagTypes: ["DatastoreConnection", "Dataset", "Datasets"],
endpoints: () => ({}),
});
9 changes: 2 additions & 7 deletions clients/admin-ui/src/features/dataset/dataset.slice.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSelector, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

import type { RootState } from "~/app/store";
import { baseApi } from "~/features/common/api.slice";
import {
BulkPutDataset,
Dataset,
Expand All @@ -27,12 +27,7 @@ interface DatasetDeleteResponse {
resource: Dataset;
}

export const datasetApi = createApi({
reducerPath: "datasetApi",
baseQuery: fetchBaseQuery({
baseUrl: process.env.NEXT_PUBLIC_FIDESCTL_API,
}),
tagTypes: ["Dataset", "Datasets"],
const datasetApi = baseApi.injectEndpoints({
endpoints: (build) => ({
getAllDatasets: build.query<Dataset[], void>({
query: () => ({ url: `dataset/` }),
Expand Down
Loading