diff --git a/CHANGELOG.md b/CHANGELOG.md index 78619f2..edf0038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## 2.1.2 + +### Security + +- **Dependency upgrades for Snyk findings.** Bumps `react-router`, `glob`, and `ajv`/`fast-uri` to patched versions that address reported CSRF, brace-expansion, and URI-parsing vulnerabilities. + +### UX improvements + +- **Fix: Model Lineage opens the correct view on first use.** Opening Model Lineage from a model `.sql`, `.model.json`, or `.yml` file (context menu, editor title, or `Cmd+Shift+L`) now shows the model lineage graph in Data Explorer instead of Column Lineage the first time the panel opens. + ## 2.1.1 ### Airflow diff --git a/DEVELOPMENT_SETUP.md b/DEVELOPMENT_SETUP.md index 1d40e1b..39c3dd8 100644 --- a/DEVELOPMENT_SETUP.md +++ b/DEVELOPMENT_SETUP.md @@ -23,7 +23,9 @@ For general environment setup (Trino, Java, dbt, etc.), see the **[Setup Guide]( For development, you'll also need: -- **Node.js** (v20.x - v22.x LTS recommended) + + +- **Node.js** (v22.22+ or v24.x LTS recommended) - **npm** (10.x or higher) - **VS Code** (version 1.87.0 or higher) diff --git a/jest.config.js b/jest.config.js index d4e73df..b73bc2e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,7 @@ const config = { preset: 'ts-jest', moduleNameMapper: { + '^vscode$': '/src/__mocks__/vscode.js', admin: ['/src/admin'], '@services/(.*)': ['/src/services/$1'], '@services': ['/src/services'], @@ -24,6 +25,7 @@ const config = { '/.venv/', '/__tests__/helpers\\.ts$', ], + modulePathIgnorePatterns: ['/out/', '/dist/'], }; module.exports = config; diff --git a/package-lock.json b/package-lock.json index b32e056..127f334 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "dj", - "version": "2.1.1", + "version": "2.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dj", - "version": "2.1.1", + "version": "2.1.2", "license": "Apache-2.0", "workspaces": [ "web" ], "dependencies": { - "ajv": "^8.17.1", + "ajv": "^8.20.0", "dompurify": "^3.2.6", - "glob": "^11.0.3", + "glob": "^12.0.0", "html-react-parser": "^5.2.5", "jsonc-parser": "^3.3.1", "lodash": "^4.17.21", @@ -4788,9 +4788,9 @@ "license": "BSD-2-Clause" }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.9", + "resolved": "https://artifactory.workday.com/artifactory/api/npm/npm-virtual/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -5354,18 +5354,11 @@ "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } + "node_modules/cookie-es": { + "version": "3.1.1", + "resolved": "https://artifactory.workday.com/artifactory/api/npm/npm-virtual/cookie-es/-/cookie-es-3.1.1.tgz", + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", + "license": "MIT" }, "node_modules/copy-to-clipboard": { "version": "3.3.3", @@ -6903,9 +6896,9 @@ "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://artifactory.workday.com/artifactory/api/npm/npm-virtual/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", @@ -7300,10 +7293,9 @@ "optional": true }, "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "version": "12.0.0", + "resolved": "https://artifactory.workday.com/artifactory/api/npm/npm-virtual/glob/-/glob-12.0.0.tgz", + "integrity": "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==", "license": "BlueOak-1.0.0", "dependencies": { "foreground-child": "^3.3.1", @@ -11432,20 +11424,19 @@ } }, "node_modules/react-router": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", - "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "version": "8.3.0", + "resolved": "https://artifactory.workday.com/artifactory/api/npm/npm-virtual/react-router/-/react-router-8.3.0.tgz", + "integrity": "sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==", "license": "MIT", "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" + "cookie-es": "^3.1.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.22.0" }, "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" + "react": ">=19.2.7", + "react-dom": ">=19.2.7" }, "peerDependenciesMeta": { "react-dom": { @@ -12116,12 +12107,6 @@ "node": ">=10" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -15320,10 +15305,10 @@ "@xyflow/react": "^12.8.6", "diff": "^8.0.3", "driver.js": "^1.3.6", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", "react-hook-form": "^7.52.1", - "react-router": "^7.3.0", + "react-router": "^8.3.0", "react-syntax-highlighter": "^16.1.0", "react-use": "^17.5.0", "zustand": "^4.5.7" diff --git a/package.json b/package.json index a8f028d..fd70e36 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "type": "git", "url": "https://github.com/Workday/dj.git" }, - "version": "2.1.1", + "version": "2.1.2", "workspaces": [ "web" ], @@ -1012,9 +1012,9 @@ "watch:tsc": "tsc --noEmit --watch --project tsconfig.json" }, "dependencies": { - "ajv": "^8.17.1", + "ajv": "^8.20.0", "dompurify": "^3.2.6", - "glob": "^11.0.3", + "glob": "^12.0.0", "html-react-parser": "^5.2.5", "jsonc-parser": "^3.3.1", "lodash": "^4.17.21", @@ -1049,7 +1049,7 @@ "typescript-eslint": "^8.50.1" }, "overrides": { - "fast-uri": "3.1.4", - "brace-expansion@5": "5.0.8" + "fast-uri": "3.1.5", + "brace-expansion@5": "5.0.9" } } \ No newline at end of file diff --git a/src/__mocks__/vscode.js b/src/__mocks__/vscode.js new file mode 100644 index 0000000..e872547 --- /dev/null +++ b/src/__mocks__/vscode.js @@ -0,0 +1,66 @@ +/** + * Jest stub for the VS Code extension API. Extension code imports `vscode` at + * module load; Node/Jest has no real module, so tests map here via jest.config.js. + */ + +class ThemeIcon { + constructor(id) { + this.id = id; + } +} + +const ConfigurationTarget = { + Global: 1, + Workspace: 2, + WorkspaceFolder: 3, +}; + +class Uri { + static file(fsPath) { + return { fsPath }; + } +} + +const configuration = { + get: (_key, defaultValue) => defaultValue, + update: async () => undefined, +}; + +const workspace = { + workspaceFolders: [{ uri: { fsPath: process.cwd() } }], + findFiles: async () => [], + getConfiguration: () => configuration, + fs: { + readFile: async () => new Uint8Array(), + writeFile: async () => undefined, + createDirectory: async () => undefined, + stat: async () => ({ type: 1 }), + delete: async () => undefined, + }, + onDidSaveTextDocument: () => ({ dispose: () => undefined }), + createFileSystemWatcher: () => ({ + onDidCreate: () => ({ dispose: () => undefined }), + onDidChange: () => ({ dispose: () => undefined }), + onDidDelete: () => ({ dispose: () => undefined }), + dispose: () => undefined, + }), +}; + +const window = { + tabGroups: { all: [], onDidChangeTabs: () => ({ dispose: () => undefined }) }, + showErrorMessage: async () => undefined, + setStatusBarMessage: () => ({ dispose: () => undefined }), +}; + +const commands = { + executeCommand: async () => undefined, +}; + +module.exports = { + ThemeIcon, + ConfigurationTarget, + Uri, + workspace, + window, + commands, +}; diff --git a/src/services/columnLineage.ts b/src/services/columnLineage.ts index 536f8c9..5a541f3 100644 --- a/src/services/columnLineage.ts +++ b/src/services/columnLineage.ts @@ -248,9 +248,12 @@ export class ColumnLineageService implements DJService { if (this.lastStatus) { this.sendStatus(this.lastStatus.message, this.lastStatus.variant); } - void this.handleAutoRefresh(vscode.window.activeTextEditor, { - retry: true, - }); + // Only auto-refresh when restoring prior column state — not on cold panel open. + if (this.lastContext || this.lastSourceContext) { + void this.handleAutoRefresh(vscode.window.activeTextEditor, { + retry: true, + }); + } } public sendConfig(): void { diff --git a/src/services/trino/__tests__/profiles.test.ts b/src/services/trino/__tests__/profiles.test.ts index 317008e..0cf4d27 100644 --- a/src/services/trino/__tests__/profiles.test.ts +++ b/src/services/trino/__tests__/profiles.test.ts @@ -1,24 +1,5 @@ import { afterEach, describe, expect, it, jest } from '@jest/globals'; -// Mock vscode before importing anything that pulls it in transitively. -// The profiles module only relies on `vscode.workspace.getConfiguration` for -// the (settings-bound) profile list + active-profile pointer, and -// `vscode.ConfigurationTarget.Workspace` as an enum constant. Stubbing those -// is enough for the pure-logic tests below. -jest.mock( - 'vscode', - () => ({ - workspace: { - getConfiguration: () => ({ - get: (_key: string, fallback: T) => fallback, - update: jest.fn(), - }), - }, - ConfigurationTarget: { Workspace: 2 }, - }), - { virtual: true }, -); - import { resolveProfileSecret, TrinoProfileError, diff --git a/web/package.json b/web/package.json index 848d22d..8110333 100644 --- a/web/package.json +++ b/web/package.json @@ -28,10 +28,10 @@ "@xyflow/react": "^12.8.6", "diff": "^8.0.3", "driver.js": "^1.3.6", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", "react-hook-form": "^7.52.1", - "react-router": "^7.3.0", + "react-router": "^8.3.0", "react-syntax-highlighter": "^16.1.0", "react-use": "^17.5.0", "zustand": "^4.5.7" diff --git a/web/src/context/app.tsx b/web/src/context/app.tsx index 4a585f8..2973c76 100644 --- a/web/src/context/app.tsx +++ b/web/src/context/app.tsx @@ -23,9 +23,9 @@ import { BrowserRouter, createBrowserRouter, Route, - RouterProvider, Routes, } from 'react-router'; +import { RouterProvider } from 'react-router/dom'; import { useMount, useUnmount } from 'react-use'; import * as uuid from 'uuid'; diff --git a/web/src/pages/DataExplorer/DataExplorer.tsx b/web/src/pages/DataExplorer/DataExplorer.tsx index 26e9931..27f66f6 100644 --- a/web/src/pages/DataExplorer/DataExplorer.tsx +++ b/web/src/pages/DataExplorer/DataExplorer.tsx @@ -70,7 +70,7 @@ export default function DataExplorer() {
- + {activeView === 'column' ? : null}