diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000000..ca9fd48b1da --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,97 @@ +name: CI + +on: + workflow_call: + workflow_dispatch: + inputs: + branch: + description: "(Optional) Branch to checkout" + required: false + type: string + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + path-filter: + name: Filter Paths + runs-on: ubuntu-latest + outputs: + python: ${{ steps.filter.outputs.python }} + frontend: ${{ steps.filter.outputs.frontend }} + docs: ${{ steps.filter.outputs.docs }} + tests: ${{ steps.filter.outputs.tests }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || github.ref }} + - name: Filter Paths + id: filter + uses: dorny/paths-filter@v3 + with: + filters: | + python: + - "src/backend/**" + - "src/backend/**.py" + - "pyproject.toml" + - "poetry.lock" + - "**/python_test.yml" + tests: + - "tests/**" + - "src/frontend/tests/**" + frontend: + - "src/frontend/**" + - "**/typescript_test.yml" + docs: + - "docs/**" + + test-backend: + needs: path-filter + name: Run Backend Tests + if: ${{ needs.path-filter.outputs.python == 'true' || needs.path-filter.outputs.tests == 'true' }} + uses: ./.github/workflows/python_test.yml + + + + test-frontend: + needs: path-filter + name: Run Frontend Tests + if: ${{ needs.path-filter.outputs.python == 'true' || needs.path-filter.outputs.frontend == 'true' || needs.path-filter.outputs.tests == 'true' }} + uses: ./.github/workflows/typescript_test.yml + + + lint-backend: + needs: path-filter + if: ${{ needs.path-filter.outputs.python == 'true' || needs.path-filter.outputs.tests == 'true' }} + name: Lint Backend + uses: ./.github/workflows/lint-py.yml + # Run only if there are python files changed + + test-docs-build: + needs: path-filter + if: ${{ needs.path-filter.outputs.docs == 'true' }} + name: Test Docs Build + uses: ./.github/workflows/docs_test.yml + + + # https://github.com/langchain-ai/langchain/blob/master/.github/workflows/check_diffs.yml + ci_success: + name: "CI Success" + needs: [test-backend, test-frontend, lint-backend, test-docs-build] + if: | + always() + runs-on: ubuntu-latest + env: + JOBS_JSON: ${{ toJSON(needs) }} + RESULTS_JSON: ${{ toJSON(needs.*.result) }} + EXIT_CODE: ${{!contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && '0' || '1'}} + steps: + - name: "CI Success" + run: | + echo $JOBS_JSON + echo $RESULTS_JSON + echo "Exiting with $EXIT_CODE" + exit $EXIT_CODE \ No newline at end of file diff --git a/.github/workflows/docs_test.yml b/.github/workflows/docs_test.yml index e7d1bf64f1e..2792017b46c 100644 --- a/.github/workflows/docs_test.yml +++ b/.github/workflows/docs_test.yml @@ -1,9 +1,13 @@ name: Test Docs Build on: - pull_request: - paths: - - "docs/**" + workflow_call: + workflow_dispatch: + inputs: + branch: + description: "(Optional) Branch to checkout" + required: false + type: string env: NODE_VERSION: "21" @@ -17,7 +21,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - + with: + ref: ${{ inputs.branch || github.ref }} - name: Setup Node.js uses: actions/setup-node@v4 id: setup-node diff --git a/.github/workflows/js_autofix.yml b/.github/workflows/js_autofix.yml index d6e3c0097ea..205bc2c0153 100644 --- a/.github/workflows/js_autofix.yml +++ b/.github/workflows/js_autofix.yml @@ -2,7 +2,7 @@ name: autofix.ci on: pull_request: - types: [opened, synchronize, reopened, auto_merge_enabled] + types: [opened, synchronize, reopened] paths: - "src/frontend/**" diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 07f8a0bc064..d7f79738dac 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -1,13 +1,14 @@ name: Lint Frontend on: - pull_request: - paths: - - "src/frontend/**" + workflow_call: + workflow_dispatch: + inputs: + branch: + description: "(Optional) Branch to checkout" + required: false + type: string -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true env: NODE_VERSION: "21" @@ -22,6 +23,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || github.ref }} - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/lint-py.yml b/.github/workflows/lint-py.yml index d1b117a710e..e63a0e82960 100644 --- a/.github/workflows/lint-py.yml +++ b/.github/workflows/lint-py.yml @@ -1,14 +1,16 @@ name: Lint Python on: - pull_request: - types: [opened, synchronize, reopened, auto_merge_enabled] + workflow_call: + workflow_dispatch: + inputs: + branch: + description: "(Optional) Branch to checkout" + required: false + type: string env: POETRY_VERSION: "1.8.2" -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true jobs: lint: @@ -22,6 +24,8 @@ jobs: - "3.10" steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || github.ref }} - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} uses: "./.github/actions/poetry_caching" with: diff --git a/.github/workflows/python_test.yml b/.github/workflows/python_test.yml index 0790f2e0e94..c91ee8d7429 100644 --- a/.github/workflows/python_test.yml +++ b/.github/workflows/python_test.yml @@ -2,15 +2,15 @@ name: Python tests on: workflow_call: - pull_request: - types: [opened, synchronize, reopened, auto_merge_enabled] - branches: [main] + workflow_dispatch: + inputs: + branch: + description: "(Optional) Branch to checkout" + required: false + type: string env: POETRY_VERSION: "1.8.2" -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ !contains(github.workflow, 'Release')}} jobs: build: @@ -26,6 +26,8 @@ jobs: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || github.ref }} - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} uses: "./.github/actions/poetry_caching" with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b2cb675dc6..0ece7621494 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,17 +20,13 @@ env: POETRY_VERSION: "1.8.2" jobs: - test_frontend: - name: Call Typescript Test Workflow - uses: langflow-ai/langflow/.github/workflows/typescript_test.yml@main - - test_backend: - name: Call Python Test Workflow - uses: langflow-ai/langflow/.github/workflows/python_test.yml@main + ci: + name: CI + uses: ./.github/workflows/ci.yml release-base: name: Release Langflow Base - needs: [test_backend, test_frontend] + needs: [ci] if: inputs.release_package == true runs-on: ubuntu-latest outputs: diff --git a/.github/workflows/style-check-py.yml b/.github/workflows/style-check-py.yml index 1264b61013d..a7886b95006 100644 --- a/.github/workflows/style-check-py.yml +++ b/.github/workflows/style-check-py.yml @@ -4,9 +4,7 @@ on: pull_request: types: [opened, synchronize, reopened, auto_merge_enabled] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + env: POETRY_VERSION: "1.8.2" diff --git a/.github/workflows/typescript_test.yml b/.github/workflows/typescript_test.yml index 7e545b45dd2..b7a48bb02f7 100644 --- a/.github/workflows/typescript_test.yml +++ b/.github/workflows/typescript_test.yml @@ -8,12 +8,7 @@ on: description: "(Optional) Branch to checkout" required: false type: string - pull_request: - types: [opened, synchronize, reopened, auto_merge_enabled] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ !contains(github.workflow, 'Release')}} env: POETRY_VERSION: "1.8.3" @@ -120,7 +115,6 @@ jobs: merge-reports: needs: setup-and-test runs-on: ubuntu-latest - if: always() steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 22af2a68fcd..a0f84292e45 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1230,7 +1230,7 @@ def get_successors(vertex, recursive=True): stack.append(successor.id) else: excluded.add(successor.id) - all_successors = get_successors(successor) + all_successors = get_successors(successor, recursive=False) for successor in all_successors: if is_start: stack.append(successor.id) diff --git a/src/frontend/tests/end-to-end/deleteComponents.spec.ts b/src/frontend/tests/end-to-end/deleteComponents.spec.ts index faf90600301..70b09303f3f 100644 --- a/src/frontend/tests/end-to-end/deleteComponents.spec.ts +++ b/src/frontend/tests/end-to-end/deleteComponents.spec.ts @@ -1,6 +1,10 @@ import { test } from "@playwright/test"; -test("should add API-KEY", async ({ page }) => { +test("should delete a component", async ({ page }) => { + test.skip( + !process?.env?.STORE_API_KEY, + "STORE_API_KEY required to run this test", + ); await page.goto("/"); await page.waitForTimeout(1000); @@ -8,29 +12,16 @@ test("should add API-KEY", async ({ page }) => { await page.waitForTimeout(1000); await page.getByTestId("api-key-button-store").click(); - await page - .getByPlaceholder("Insert your API Key") - .fill("testtesttesttesttesttest"); - - await page.getByTestId("api-key-save-button-store").click(); - - await page.waitForTimeout(2000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); await page .getByPlaceholder("Insert your API Key") .fill(process.env.STORE_API_KEY ?? ""); + await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(2000); await page.getByText("Success! Your API Key has been saved.").isVisible(); - await page.waitForTimeout(2000); - await page.getByText("API Key Error").isHidden(); -}); - -test("should delete a component", async ({ page }) => { - await page.goto("/"); await page.waitForTimeout(2000); await page.getByText("Store").nth(0).click(); await page.getByTestId("install-Basic RAG").click(); diff --git a/src/frontend/tests/end-to-end/deleteFlows.spec.ts b/src/frontend/tests/end-to-end/deleteFlows.spec.ts index 0b168e9eec6..a5824517d18 100644 --- a/src/frontend/tests/end-to-end/deleteFlows.spec.ts +++ b/src/frontend/tests/end-to-end/deleteFlows.spec.ts @@ -1,6 +1,10 @@ import { test } from "@playwright/test"; -test("should add API-KEY", async ({ page }) => { +test("should delete a flow", async ({ page }) => { + test.skip( + !process?.env?.STORE_API_KEY, + "STORE_API_KEY required to run this test", + ); await page.goto("/"); await page.waitForTimeout(1000); @@ -8,31 +12,16 @@ test("should add API-KEY", async ({ page }) => { await page.waitForTimeout(1000); await page.getByTestId("api-key-button-store").click(); - await page - .getByPlaceholder("Insert your API Key") - .fill("testtesttesttesttesttest"); - - await page.getByTestId("api-key-save-button-store").click(); - - await page.waitForTimeout(2000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); await page .getByPlaceholder("Insert your API Key") .fill(process.env.STORE_API_KEY ?? ""); + await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(2000); await page.getByText("Success! Your API Key has been saved.").isVisible(); - await page.waitForTimeout(2000); - await page.getByText("API Key Error").isHidden(); -}); - -test("should delete a flow", async ({ page }) => { - await page.goto("/"); - await page.waitForTimeout(2000); - await page.waitForSelector("text=Store", { timeout: 30000 }); await page.getByText("Store").nth(0).click(); diff --git a/src/frontend/src/logs.spec.ts b/src/frontend/tests/end-to-end/logs.spec.ts similarity index 96% rename from src/frontend/src/logs.spec.ts rename to src/frontend/tests/end-to-end/logs.spec.ts index f3f0a7c9542..3eb697711eb 100644 --- a/src/frontend/src/logs.spec.ts +++ b/src/frontend/tests/end-to-end/logs.spec.ts @@ -1,6 +1,11 @@ import { test } from "@playwright/test"; test("should able to see and interact with logs", async ({ page }) => { + test.skip( + !process?.env?.OPENAI_API_KEY, + "OPENAI_API_KEY required to run this test", + ); + await page.goto("/"); await page.waitForTimeout(2000); @@ -20,7 +25,6 @@ test("should able to see and interact with logs", async ({ page }) => { await page.waitForTimeout(5000); modalCount = await page.getByTestId("modal-title")?.count(); } - await page.waitForTimeout(1000); await page.getByRole("heading", { name: "Basic Prompting" }).click(); await page.waitForTimeout(2000); diff --git a/src/frontend/tests/end-to-end/store-shard-1.spec.ts b/src/frontend/tests/end-to-end/store-shard-1.spec.ts index 00bd9025014..10bef89c7e0 100644 --- a/src/frontend/tests/end-to-end/store-shard-1.spec.ts +++ b/src/frontend/tests/end-to-end/store-shard-1.spec.ts @@ -1,35 +1,10 @@ import { expect, test } from "@playwright/test"; -test("should add API-KEY", async ({ page }) => { - await page.goto("/"); - await page.waitForTimeout(1000); - - await page.getByTestId("button-store").click(); - await page.waitForTimeout(1000); - - await page.getByTestId("api-key-button-store").click(); - await page - .getByPlaceholder("Insert your API Key") - .fill("testtesttesttesttesttest"); - - await page.getByTestId("api-key-save-button-store").click(); - - await page.waitForTimeout(2000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); - - await page - .getByPlaceholder("Insert your API Key") - .fill(process.env.STORE_API_KEY ?? ""); - await page.getByTestId("api-key-save-button-store").click(); - - await page.waitForTimeout(2000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); - - await page.waitForTimeout(2000); - await page.getByText("API Key Error").isHidden(); -}); - test("should like and add components and flows", async ({ page }) => { + test.skip( + !process?.env?.STORE_API_KEY, + "STORE_API_KEY required to run this test", + ); await page.goto("/"); await page.waitForTimeout(1000); @@ -41,14 +16,12 @@ test("should like and add components and flows", async ({ page }) => { await page .getByPlaceholder("Insert your API Key") .fill(process.env.STORE_API_KEY ?? ""); + await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(2000); await page.getByText("Success! Your API Key has been saved.").isVisible(); - await page.waitForTimeout(2000); - await page.getByText("API Key Error").isHidden(); - await page.waitForTimeout(2000); await page.getByTestId("button-store").click(); @@ -99,9 +72,29 @@ test("should like and add components and flows", async ({ page }) => { }); test("should find a searched Component on Store", async ({ page }) => { + test.skip( + !process?.env?.STORE_API_KEY, + "STORE_API_KEY required to run this test", + ); await page.goto("/"); await page.waitForTimeout(1000); + await page.getByTestId("button-store").click(); + await page.waitForTimeout(1000); + + await page.getByTestId("api-key-button-store").click(); + + await page + .getByPlaceholder("Insert your API Key") + .fill(process.env.STORE_API_KEY ?? ""); + + await page.getByTestId("api-key-save-button-store").click(); + + await page.waitForTimeout(2000); + await page.getByText("Success! Your API Key has been saved.").isVisible(); + + await page.waitForTimeout(1000); + await page.getByTestId("button-store").click(); await page.waitForSelector('[data-testid="search-store-input"]', { diff --git a/src/frontend/tests/end-to-end/store-shard-2.spec.ts b/src/frontend/tests/end-to-end/store-shard-2.spec.ts index a39ca5a0281..d084ef48ab2 100644 --- a/src/frontend/tests/end-to-end/store-shard-2.spec.ts +++ b/src/frontend/tests/end-to-end/store-shard-2.spec.ts @@ -1,6 +1,10 @@ import { expect, test } from "@playwright/test"; -test("should add API-KEY", async ({ page }) => { +test("should filter by tag", async ({ page }) => { + test.skip( + !process?.env?.STORE_API_KEY, + "STORE_API_KEY required to run this test", + ); await page.goto("/"); await page.waitForTimeout(1000); @@ -8,31 +12,16 @@ test("should add API-KEY", async ({ page }) => { await page.waitForTimeout(1000); await page.getByTestId("api-key-button-store").click(); - await page - .getByPlaceholder("Insert your API Key") - .fill("testtesttesttesttesttest"); - - await page.getByTestId("api-key-save-button-store").click(); - - await page.waitForTimeout(2000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); await page .getByPlaceholder("Insert your API Key") .fill(process.env.STORE_API_KEY ?? ""); + await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(2000); await page.getByText("Success! Your API Key has been saved.").isVisible(); - await page.waitForTimeout(2000); - await page.getByText("API Key Error").isHidden(); -}); - -test("should filter by tag", async ({ page }) => { - await page.goto("/"); - await page.waitForTimeout(1000); - await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); @@ -57,7 +46,28 @@ test("should filter by tag", async ({ page }) => { }); test("should share component with share button", async ({ page }) => { + test.skip( + !process?.env?.STORE_API_KEY, + "STORE_API_KEY required to run this test", + ); await page.goto("/"); + await page.waitForTimeout(1000); + + await page.getByTestId("button-store").click(); + await page.waitForTimeout(1000); + + await page.getByTestId("api-key-button-store").click(); + + await page + .getByPlaceholder("Insert your API Key") + .fill(process.env.STORE_API_KEY ?? ""); + + await page.getByTestId("api-key-save-button-store").click(); + + await page.waitForTimeout(2000); + await page.getByText("Success! Your API Key has been saved.").isVisible(); + + await page.getByText("My Collection").click(); await page.waitForTimeout(2000); let modalCount = 0; diff --git a/src/frontend/tests/end-to-end/store-shard-3.spec.ts b/src/frontend/tests/end-to-end/store-shard-3.spec.ts index c1937942111..5612baf56ed 100644 --- a/src/frontend/tests/end-to-end/store-shard-3.spec.ts +++ b/src/frontend/tests/end-to-end/store-shard-3.spec.ts @@ -1,6 +1,10 @@ import { expect, test } from "@playwright/test"; -test("should add API-KEY", async ({ page }) => { +test("should order the visualization", async ({ page }) => { + test.skip( + !process?.env?.STORE_API_KEY, + "STORE_API_KEY required to run this test", + ); await page.goto("/"); await page.waitForTimeout(1000); @@ -8,29 +12,16 @@ test("should add API-KEY", async ({ page }) => { await page.waitForTimeout(1000); await page.getByTestId("api-key-button-store").click(); - await page - .getByPlaceholder("Insert your API Key") - .fill("testtesttesttesttesttest"); - - await page.getByTestId("api-key-save-button-store").click(); - - await page.waitForTimeout(2000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); await page .getByPlaceholder("Insert your API Key") .fill(process.env.STORE_API_KEY ?? ""); + await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(2000); await page.getByText("Success! Your API Key has been saved.").isVisible(); - await page.waitForTimeout(2000); - await page.getByText("API Key Error").isHidden(); -}); - -test("should order the visualization", async ({ page }) => { - await page.goto("/"); await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); @@ -51,12 +42,30 @@ test("should order the visualization", async ({ page }) => { }); test("should filter by type", async ({ page }) => { + test.skip( + !process?.env?.STORE_API_KEY, + "STORE_API_KEY required to run this test", + ); await page.goto("/"); await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); + await page.getByTestId("api-key-button-store").click(); + + await page + .getByPlaceholder("Insert your API Key") + .fill(process.env.STORE_API_KEY ?? ""); + + await page.getByTestId("api-key-save-button-store").click(); + + await page.waitForTimeout(2000); + await page.getByText("Success! Your API Key has been saved.").isVisible(); + + await page.getByTestId("button-store").click(); + await page.waitForTimeout(1000); + await page.getByText("Website Content QA").isVisible(); await page.getByTestId("flows-button-store").click(); diff --git a/src/frontend/tsconfig.json b/src/frontend/tsconfig.json index bdf14f2c3c8..52a6d1325f8 100644 --- a/src/frontend/tsconfig.json +++ b/src/frontend/tsconfig.json @@ -52,6 +52,7 @@ "tests/end-to-end/tweaks_test.spec.ts", "tests/end-to-end/twoEdges.spec.ts", "tests/end-to-end/userSettings.spec.ts", - "tests/end-to-end/store.spec.ts" + "tests/end-to-end/store.spec.ts", + "tests/end-to-end/logs.spec.ts" ] }