Spaces:
Running
Running
59 fix default workspace env (#68)
Browse files* Fix default workspace environment
---------
Co-authored-by: JMLizano <[email protected]>
- lynxkite-app/src/lynxkite/app/crdt.py +2 -2
- lynxkite-app/web/playwright.config.ts +2 -2
- lynxkite-app/web/src/Directory.tsx +2 -4
- lynxkite-app/web/tests/basic.spec.ts +1 -1
- lynxkite-app/web/tests/directory.spec.ts +0 -1
- lynxkite-app/web/tests/examples.spec.ts +3 -3
- lynxkite-app/web/tests/lynxkite.ts +10 -11
lynxkite-app/src/lynxkite/app/crdt.py
CHANGED
@@ -11,7 +11,7 @@ import pycrdt_websocket
|
|
11 |
import pycrdt_websocket.ystore
|
12 |
import uvicorn
|
13 |
import builtins
|
14 |
-
from lynxkite.core import workspace
|
15 |
|
16 |
router = fastapi.APIRouter()
|
17 |
DATA_PATH = pathlib.Path.cwd() / "data"
|
@@ -52,7 +52,7 @@ class WebsocketServer(pycrdt_websocket.WebsocketServer):
|
|
52 |
if "edges" not in ws:
|
53 |
ws["edges"] = pycrdt.Array()
|
54 |
if "env" not in ws:
|
55 |
-
ws["env"] =
|
56 |
# We have two possible sources of truth for the workspaces, the YStore and the JSON files.
|
57 |
# In case we didn't find the workspace in the YStore, we try to load it from the JSON files.
|
58 |
try_to_load_workspace(ws, name)
|
|
|
11 |
import pycrdt_websocket.ystore
|
12 |
import uvicorn
|
13 |
import builtins
|
14 |
+
from lynxkite.core import workspace, ops
|
15 |
|
16 |
router = fastapi.APIRouter()
|
17 |
DATA_PATH = pathlib.Path.cwd() / "data"
|
|
|
52 |
if "edges" not in ws:
|
53 |
ws["edges"] = pycrdt.Array()
|
54 |
if "env" not in ws:
|
55 |
+
ws["env"] = next(iter(ops.CATALOGS), 'unset')
|
56 |
# We have two possible sources of truth for the workspaces, the YStore and the JSON files.
|
57 |
# In case we didn't find the workspace in the YStore, we try to load it from the JSON files.
|
58 |
try_to_load_workspace(ws, name)
|
lynxkite-app/web/playwright.config.ts
CHANGED
@@ -3,7 +3,7 @@ import { defineConfig, devices } from '@playwright/test';
|
|
3 |
|
4 |
export default defineConfig({
|
5 |
testDir: './tests',
|
6 |
-
timeout:
|
7 |
fullyParallel: false,
|
8 |
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
9 |
forbidOnly: !!process.env.CI,
|
@@ -26,6 +26,6 @@ export default defineConfig({
|
|
26 |
webServer: {
|
27 |
command: 'cd .. && lynxkite',
|
28 |
url: 'http://127.0.0.1:8000',
|
29 |
-
reuseExistingServer:
|
30 |
},
|
31 |
});
|
|
|
3 |
|
4 |
export default defineConfig({
|
5 |
testDir: './tests',
|
6 |
+
timeout: 30000,
|
7 |
fullyParallel: false,
|
8 |
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
9 |
forbidOnly: !!process.env.CI,
|
|
|
26 |
webServer: {
|
27 |
command: 'cd .. && lynxkite',
|
28 |
url: 'http://127.0.0.1:8000',
|
29 |
+
reuseExistingServer: false,
|
30 |
},
|
31 |
});
|
lynxkite-app/web/src/Directory.tsx
CHANGED
@@ -25,7 +25,7 @@ const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
|
25 |
export default function () {
|
26 |
const { path } = useParams();
|
27 |
const encodedPath = encodeURIComponent(path || '');
|
28 |
-
|
29 |
const navigate = useNavigate();
|
30 |
const [isCreatingDir, setIsCreatingDir] = useState(false);
|
31 |
const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false);
|
@@ -70,7 +70,6 @@ export default function () {
|
|
70 |
headers: { 'Content-Type': 'application/json' },
|
71 |
body: JSON.stringify({ path: pathSlash + name }),
|
72 |
});
|
73 |
-
list = await res.json();
|
74 |
if (res.ok) {
|
75 |
navigate(`/dir/${pathSlash}${name}`);
|
76 |
} else {
|
@@ -83,12 +82,11 @@ export default function () {
|
|
83 |
const pathSlash = path ? `${path}/` : "";
|
84 |
|
85 |
const apiPath = item.type === "directory" ? `/api/dir/delete`: `/api/delete`;
|
86 |
-
|
87 |
method: "POST",
|
88 |
headers: { "Content-Type": "application/json" },
|
89 |
body: JSON.stringify({ path: pathSlash + item.name }),
|
90 |
});
|
91 |
-
list = await res.json();
|
92 |
}
|
93 |
|
94 |
return (
|
|
|
25 |
export default function () {
|
26 |
const { path } = useParams();
|
27 |
const encodedPath = encodeURIComponent(path || '');
|
28 |
+
const list = useSWR(`/api/dir/list?path=${encodedPath}`, fetcher);
|
29 |
const navigate = useNavigate();
|
30 |
const [isCreatingDir, setIsCreatingDir] = useState(false);
|
31 |
const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false);
|
|
|
70 |
headers: { 'Content-Type': 'application/json' },
|
71 |
body: JSON.stringify({ path: pathSlash + name }),
|
72 |
});
|
|
|
73 |
if (res.ok) {
|
74 |
navigate(`/dir/${pathSlash}${name}`);
|
75 |
} else {
|
|
|
82 |
const pathSlash = path ? `${path}/` : "";
|
83 |
|
84 |
const apiPath = item.type === "directory" ? `/api/dir/delete`: `/api/delete`;
|
85 |
+
await fetch(apiPath, {
|
86 |
method: "POST",
|
87 |
headers: { "Content-Type": "application/json" },
|
88 |
body: JSON.stringify({ path: pathSlash + item.name }),
|
89 |
});
|
|
|
90 |
}
|
91 |
|
92 |
return (
|
lynxkite-app/web/tests/basic.spec.ts
CHANGED
@@ -10,7 +10,7 @@ test.beforeEach(async ({ browser }) => {
|
|
10 |
workspace = await Workspace.empty(await browser.newPage(), 'basic_spec_test');
|
11 |
});
|
12 |
|
13 |
-
test.afterEach(async (
|
14 |
await workspace.close();
|
15 |
const splash = await new Splash(workspace.page);
|
16 |
splash.page.on('dialog', async dialog => { await dialog.accept(); });
|
|
|
10 |
workspace = await Workspace.empty(await browser.newPage(), 'basic_spec_test');
|
11 |
});
|
12 |
|
13 |
+
test.afterEach(async () => {
|
14 |
await workspace.close();
|
15 |
const splash = await new Splash(workspace.page);
|
16 |
splash.page.on('dialog', async dialog => { await dialog.accept(); });
|
lynxkite-app/web/tests/directory.spec.ts
CHANGED
@@ -20,7 +20,6 @@ test.describe("Directory operations", () => {
|
|
20 |
// Not checking for exact match, since there may be pre-existing "Untitled" workspaces
|
21 |
expect(workspace.name).toContain('Untitled');
|
22 |
await workspace.close();
|
23 |
-
await splash.deleteEntry(workspace.name);
|
24 |
});
|
25 |
|
26 |
test('Create & delete workspace', async () => {
|
|
|
20 |
// Not checking for exact match, since there may be pre-existing "Untitled" workspaces
|
21 |
expect(workspace.name).toContain('Untitled');
|
22 |
await workspace.close();
|
|
|
23 |
});
|
24 |
|
25 |
test('Create & delete workspace', async () => {
|
lynxkite-app/web/tests/examples.spec.ts
CHANGED
@@ -5,7 +5,7 @@ import { Workspace } from './lynxkite';
|
|
5 |
|
6 |
test('LynxKite Graph Analytics example', async ({ page }) => {
|
7 |
const ws = await Workspace.open(page, "NetworkX demo");
|
8 |
-
expect(await ws.isErrorFree()).toBeTruthy();
|
9 |
});
|
10 |
|
11 |
|
@@ -31,7 +31,7 @@ test.fail('LynxScribe example', async ({ page }) => {
|
|
31 |
test.fail('Graph RAG', async ({ page }) => {
|
32 |
// Fails due to some issue with ChromaDB
|
33 |
const ws = await Workspace.open(page, "Graph RAG");
|
34 |
-
expect(await ws.isErrorFree()).toBeTruthy();
|
35 |
});
|
36 |
|
37 |
|
@@ -46,7 +46,7 @@ test.fail('night demo', async ({ page }) => {
|
|
46 |
// airlines.graphml file not found
|
47 |
// requires cugraph
|
48 |
const ws = await Workspace.open(page, "night demo");
|
49 |
-
expect(await ws.isErrorFree()).toBeTruthy();
|
50 |
});
|
51 |
|
52 |
|
|
|
5 |
|
6 |
test('LynxKite Graph Analytics example', async ({ page }) => {
|
7 |
const ws = await Workspace.open(page, "NetworkX demo");
|
8 |
+
expect(await ws.isErrorFree(process.env.CI? 2000: 1000)).toBeTruthy();
|
9 |
});
|
10 |
|
11 |
|
|
|
31 |
test.fail('Graph RAG', async ({ page }) => {
|
32 |
// Fails due to some issue with ChromaDB
|
33 |
const ws = await Workspace.open(page, "Graph RAG");
|
34 |
+
expect(await ws.isErrorFree(process.env.CI? 2000: 500)).toBeTruthy();
|
35 |
});
|
36 |
|
37 |
|
|
|
46 |
// airlines.graphml file not found
|
47 |
// requires cugraph
|
48 |
const ws = await Workspace.open(page, "night demo");
|
49 |
+
expect(await ws.isErrorFree(process.env.CI? 10000: 500)).toBeTruthy();
|
50 |
});
|
51 |
|
52 |
|
lynxkite-app/web/tests/lynxkite.ts
CHANGED
@@ -29,19 +29,20 @@ export class Workspace {
|
|
29 |
static async open(page: Page, workspaceName: string): Promise<Workspace> {
|
30 |
const splash = await Splash.open(page);
|
31 |
const ws = await splash.openWorkspace(workspaceName);
|
32 |
-
await ws.waitForNodesToLoad()
|
33 |
await ws.expectCurrentWorkspaceIs(workspaceName);
|
34 |
return ws
|
35 |
}
|
36 |
|
37 |
async getEnvs() {
|
38 |
// Return all available workspace environments
|
39 |
-
|
|
|
|
|
40 |
}
|
41 |
|
42 |
async setEnv(env: string) {
|
43 |
await this.page.locator('select[name="workspace-env"]').selectOption(env);
|
44 |
-
// await this.page.getByRole('combobox', {'name': 'workspace-env'}).selectOption(env);
|
45 |
}
|
46 |
|
47 |
async expectCurrentWorkspaceIs(name) {
|
@@ -50,11 +51,8 @@ export class Workspace {
|
|
50 |
|
51 |
async waitForNodesToLoad() {
|
52 |
// This method should be used only on non empty workspaces
|
53 |
-
await this.page.locator('.react-flow__nodes').waitFor(
|
54 |
-
|
55 |
-
while (nodes.length === 0) {
|
56 |
-
nodes = await this.getBoxes();
|
57 |
-
}
|
58 |
}
|
59 |
|
60 |
async addBox(boxName) {
|
@@ -131,7 +129,10 @@ export class Workspace {
|
|
131 |
await this.page.mouse.up();
|
132 |
}
|
133 |
|
134 |
-
async isErrorFree(): Promise<boolean> {
|
|
|
|
|
|
|
135 |
const boxes = await this.getBoxes();
|
136 |
for (const box of boxes) {
|
137 |
if (await box.locator('.error').isVisible()) {
|
@@ -188,8 +189,6 @@ export class Splash {
|
|
188 |
}
|
189 |
await this.page.locator('input[name="workspaceName"]').press('Enter');
|
190 |
const ws = new Workspace(this.page, workspaceName);
|
191 |
-
// Workaround until we fix the default environment
|
192 |
-
await ws.setEnv('PyTorch model');
|
193 |
await ws.setEnv('LynxKite Graph Analytics');
|
194 |
return ws;
|
195 |
}
|
|
|
29 |
static async open(page: Page, workspaceName: string): Promise<Workspace> {
|
30 |
const splash = await Splash.open(page);
|
31 |
const ws = await splash.openWorkspace(workspaceName);
|
32 |
+
await ws.waitForNodesToLoad();
|
33 |
await ws.expectCurrentWorkspaceIs(workspaceName);
|
34 |
return ws
|
35 |
}
|
36 |
|
37 |
async getEnvs() {
|
38 |
// Return all available workspace environments
|
39 |
+
const envs = this.page.locator('select[name="workspace-env"] option');
|
40 |
+
await expect(envs).not.toHaveCount(0);
|
41 |
+
return await envs.allInnerTexts();
|
42 |
}
|
43 |
|
44 |
async setEnv(env: string) {
|
45 |
await this.page.locator('select[name="workspace-env"]').selectOption(env);
|
|
|
46 |
}
|
47 |
|
48 |
async expectCurrentWorkspaceIs(name) {
|
|
|
51 |
|
52 |
async waitForNodesToLoad() {
|
53 |
// This method should be used only on non empty workspaces
|
54 |
+
await this.page.locator('.react-flow__nodes').waitFor();
|
55 |
+
await this.page.locator('.react-flow__node').first().waitFor();
|
|
|
|
|
|
|
56 |
}
|
57 |
|
58 |
async addBox(boxName) {
|
|
|
129 |
await this.page.mouse.up();
|
130 |
}
|
131 |
|
132 |
+
async isErrorFree(executionWaitTime?): Promise<boolean> {
|
133 |
+
// TODO: Workaround, to account for workspace execution. Once
|
134 |
+
// we have a load indicator we can use that instead.
|
135 |
+
await new Promise(resolve => setTimeout(resolve, executionWaitTime? executionWaitTime : 500));
|
136 |
const boxes = await this.getBoxes();
|
137 |
for (const box of boxes) {
|
138 |
if (await box.locator('.error').isVisible()) {
|
|
|
189 |
}
|
190 |
await this.page.locator('input[name="workspaceName"]').press('Enter');
|
191 |
const ws = new Workspace(this.page, workspaceName);
|
|
|
|
|
192 |
await ws.setEnv('LynxKite Graph Analytics');
|
193 |
return ws;
|
194 |
}
|