Skip to content

Commit

Permalink
feat: implement Dependencies feature
Browse files Browse the repository at this point in the history
  • Loading branch information
antoine-coulon committed Sep 8, 2023
1 parent 45f7176 commit 53dbc14
Show file tree
Hide file tree
Showing 20 changed files with 337 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AppState } from "../../store/state";

export type FileExplorerEvents =
export type FileExplorerActions =
| {
action: "filter_by_glob";
payload: {
Expand Down
44 changes: 14 additions & 30 deletions apps/web/src/core/file-system/filter-by-glob.spec.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
import { describe, test, expect } from "vitest";
import { BehaviorSubject, Observable, ReplaySubject, Subject } from "rxjs";
import { BehaviorSubject, Subject } from "rxjs";

import { AppStore } from "@/store/store";
import { AppState, storeDefaultValue } from "@/store/state";
import { filterByGlob } from "./filter-by-glob";
import reducers from "./reducers";

function toPromise<T>(observable: Observable<T>): Promise<T> {
return new Promise((resolve) => {
observable.subscribe((data) => {
resolve(data);
});
});
}
import { fileSystemReducers } from "./reducers";
import { toPromise } from "../utils";

describe("When filtering data by adding a glob", () => {
describe("When the data store is initially empty", () => {
test("Should apply the filter in the ui store but should not change anything in the data store", async () => {
const appStore = new AppStore(
new BehaviorSubject<AppState>(storeDefaultValue),
new Subject(),
reducers
fileSystemReducers
);

const dispatchAction = filterByGlob(appStore);
Expand All @@ -36,6 +28,7 @@ describe("When filtering data by adding a glob", () => {
});

expect(ui).toEqual({
...storeDefaultValue.ui,
filters: {
glob: "src/**/*.ts",
},
Expand Down Expand Up @@ -75,8 +68,7 @@ describe("When filtering data by adding a glob", () => {
},
},
}),
new Subject(),
reducers
fileSystemReducers
);

const dispatchAction = filterByGlob(appStore);
Expand All @@ -103,6 +95,7 @@ describe("When filtering data by adding a glob", () => {
});

expect(ui).toEqual({
...storeDefaultValue.ui,
filters: {
glob: "*.ts",
},
Expand All @@ -122,8 +115,7 @@ describe("When filtering data by adding a glob", () => {
graph: {},
},
}),
new Subject(),
reducers
fileSystemReducers
);

const dispatchAction = filterByGlob(appStore);
Expand All @@ -139,6 +131,7 @@ describe("When filtering data by adding a glob", () => {
});

expect(ui).toEqual({
...storeDefaultValue.ui,
filters: {
glob: "a.js",
},
Expand Down Expand Up @@ -166,17 +159,12 @@ describe("When resetting the glob to none", () => {
},
},
},
ui: {
filters: {
glob: "",
},
},
ui: storeDefaultValue.ui,
};

const appStore = new AppStore(
new BehaviorSubject<AppState>(storeDefaultValue),
new Subject(),
reducers
fileSystemReducers
);

appStore.setInitialState(initialAppState);
Expand Down Expand Up @@ -217,17 +205,12 @@ describe("When removing an initially set glob", () => {
},
},
},
ui: {
filters: {
glob: "",
},
},
ui: storeDefaultValue.ui,
};

const appStore = new AppStore(
new BehaviorSubject<AppState>(storeDefaultValue),
new Subject(),
reducers
fileSystemReducers
);

appStore.setInitialState(initialAppState);
Expand All @@ -253,6 +236,7 @@ describe("When removing an initially set glob", () => {
},
},
ui: {
...storeDefaultValue.ui,
filters: {
glob: "src/lib/**/*",
},
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/core/file-system/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function filterByGlob(): AppReducer {
...applyGlob(event.payload.glob, event.payload.data),
},
ui: {
...state.ui,
filters: {
glob: event.payload.glob,
},
Expand All @@ -52,6 +53,7 @@ function filterByGlob(): AppReducer {
return Option.some({
data: event.payload.data,
ui: {
...state.ui,
filters: {
glob: "",
},
Expand All @@ -63,4 +65,4 @@ function filterByGlob(): AppReducer {
};
}

export default [filterByGlob()];
export const fileSystemReducers = [filterByGlob()];
33 changes: 33 additions & 0 deletions apps/web/src/core/network/reducers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { AppReducer } from "@/store/reducer";
import * as Option from "@effect/data/Option";

function toggleDependencies(): AppReducer {
return function (event, state) {
if (
event.action === "toggle_circular" ||
event.action === "toggle_builtin" ||
event.action === "toggle_thirdparty"
) {
const target = event.action.split("_")[1];

return Option.some({
data: state.data,
ui: {
...state.ui,
network: {
dependencies: {
...state.ui.network.dependencies,
[target]: {
active: event.payload.enabled,
},
},
},
},
});
}

return Option.none();
};
}

export const networkReducers = [toggleDependencies()];
65 changes: 65 additions & 0 deletions apps/web/src/core/network/toggle-dependencies.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { AppState, storeDefaultValue } from "@/store/state";
import { AppStore } from "@/store/store";
import { BehaviorSubject } from "rxjs";
import { describe, expect, test } from "vitest";
import { toggleDependencies } from "./toggle-dependencies";
import { toPromise } from "../utils";
import { networkReducers } from "./reducers";

describe("When interacting with network dependencies", () => {
describe.each([
{ target: "circular" },
{ target: "builtin" },
{ target: "thirdparty" },
] as const)("When enabling $target dependencies", ({ target }) => {
test(`Should register the activated state for ${target} deps`, async () => {
const appStore = new AppStore(
new BehaviorSubject<AppState>(storeDefaultValue),
networkReducers
);

const emittedEvents: string[] = [];
const subscription = appStore.events$.subscribe((events) => {
emittedEvents.push(events.action);
});
const dispatchAction = toggleDependencies(appStore);

dispatchAction({ target });

const { ui: uiState1 } = await toPromise(appStore.store$);

expect(uiState1).toEqual({
...storeDefaultValue.ui,
network: {
dependencies: {
...storeDefaultValue.ui.network.dependencies,
[target]: {
active: true,
},
},
},
});

dispatchAction({ target });

const { ui: uiState2 } = await toPromise(appStore.store$);

expect(uiState2).toEqual({
...storeDefaultValue.ui,
network: {
dependencies: {
...storeDefaultValue.ui.network.dependencies,
[target]: {
active: false,
},
},
},
});

const appEvent = `toggle_${target}`;
expect(emittedEvents).toEqual([appEvent, appEvent]);

subscription.unsubscribe();
});
});
});
18 changes: 18 additions & 0 deletions apps/web/src/core/network/toggle-dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AppStore } from "@/store/store";

export function toggleDependencies(appStore: AppStore) {
return function (params: { target: "circular" | "builtin" | "thirdparty" }) {
const networkDependency =
appStore.getState().ui.network.dependencies[params.target];

appStore.dispatch(
{
action: `toggle_${params.target}`,
payload: {
enabled: !networkDependency.active,
},
},
{ notify: true }
);
};
}
12 changes: 12 additions & 0 deletions apps/web/src/core/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Observable } from "rxjs";

export function toPromise<T>(observable: Observable<T>): Promise<T> {
return new Promise((resolve) => {
const subscription = observable.subscribe((data) => {
resolve(data);
setTimeout(() => {
subscription.unsubscribe();
});
});
});
}
2 changes: 1 addition & 1 deletion apps/web/src/global-search/GlobalSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function GlobalSearch() {
}
});

const dataStoreSubscription = appStore.dataState$.subscribe((data) => {
const dataStoreSubscription = appStore.store$.subscribe(({ data }) => {
if (containerRef.current) {
containerRef.current.data = Object.values(data.graph)
.map((value) => ({
Expand Down
31 changes: 19 additions & 12 deletions apps/web/src/network/Network.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from "react";
import { Subscription, combineLatest, distinctUntilChanged } from "rxjs";
import { Subscription, combineLatest, distinctUntilChanged, map } from "rxjs";

import { DataSet } from "vis-data";
import { Edge, Network, Node } from "vis-network";
import isEqual from "lodash.isequal";

import { SkottStructureWithCycles, SkottStructureWithMetadata } from "../skott";
import { UiEvents } from "@/store/events";
import {
defaultEdgeOptions,
defaultNodeOptions,
Expand All @@ -24,6 +23,8 @@ import {
} from "./dependencies";
import { AppState } from "@/store/state";
import { useAppStore } from "@/store/react-bindings";
import { AppActions } from "@/store/actions";
import { AppEvents } from "@/store/events";

export function getMethodToApplyOnNetworkElement(enable: boolean) {
return enable ? "update" : "remove";
Expand Down Expand Up @@ -121,26 +122,29 @@ export default function GraphNetwork() {
edgesDataset[getMethodToApplyOnNetworkElement(enabled)](linkedEdges);
}

function networkReducer(dataStore: AppState["data"], uiEvents: UiEvents) {
switch (uiEvents.action) {
function networkUIReducer(
dataStore: AppState["data"],
appEvents: AppActions | AppEvents
) {
switch (appEvents.action) {
case "focus_on_node": {
focusOnNetworkNode(uiEvents.payload.nodeId);
focusOnNetworkNode(appEvents.payload.nodeId);
break;
}
case "toggle_circular": {
highlightCircularDependencies(dataStore, uiEvents.payload.enabled);
highlightCircularDependencies(dataStore, appEvents.payload.enabled);
if (dataStore.entrypoint) {
highlightEntrypoint(dataStore.entrypoint);
}
break;
}
case "toggle_builtin": {
toggleDependencies(dataStore, "builtin", uiEvents.payload.enabled);
toggleDependencies(dataStore, "builtin", appEvents.payload.enabled);
network?.stabilize();
break;
}
case "toggle_thirdparty": {
toggleDependencies(dataStore, "third_party", uiEvents.payload.enabled);
toggleDependencies(dataStore, "third_party", appEvents.payload.enabled);
network?.stabilize();
break;
}
Expand All @@ -151,8 +155,11 @@ export default function GraphNetwork() {
let subscription: Subscription;

if (networkContainerRef.current) {
subscription = appStore.dataState$
.pipe(distinctUntilChanged(isEqual))
subscription = appStore.store$
.pipe(
map(({ data }) => data),
distinctUntilChanged(isEqual)
)
.subscribe((data) => {
const { graphNodes, graphEdges } = makeNodesAndEdges(
Object.values(data.graph),
Expand Down Expand Up @@ -199,8 +206,8 @@ export default function GraphNetwork() {
React.useEffect(() => {
const uiEventsSubscription = combineLatest([
appStore.events$,
appStore.dataState$,
]).subscribe(([uiEvents, data]) => networkReducer(data, uiEvents));
appStore.store$,
]).subscribe(([uiEvents, { data }]) => networkUIReducer(data, uiEvents));

return () => {
uiEventsSubscription.unsubscribe();
Expand Down

0 comments on commit 53dbc14

Please sign in to comment.