mirror of
https://github.com/apache/superset.git
synced 2026-05-15 04:45:10 +00:00
Compare commits
22 Commits
mobile-das
...
single-sv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
137c9fca6d | ||
|
|
671eed7863 | ||
|
|
64dae07675 | ||
|
|
e56883baef | ||
|
|
a62bf2b0bb | ||
|
|
01224007da | ||
|
|
d1e9a5df06 | ||
|
|
48530cb888 | ||
|
|
676979643f | ||
|
|
21e62d594e | ||
|
|
5bad4f55fb | ||
|
|
17a5f69339 | ||
|
|
d690aa7eb4 | ||
|
|
d6c458abd4 | ||
|
|
c233bf6171 | ||
|
|
992f561ab9 | ||
|
|
d7fa9301cc | ||
|
|
958d4aa3de | ||
|
|
2a1dcb79e3 | ||
|
|
817814d4f6 | ||
|
|
1a7a14c357 | ||
|
|
85c4411041 |
@@ -43,6 +43,9 @@ _build/*
|
||||
_static/*
|
||||
.buildinfo
|
||||
searchindex.js
|
||||
# auto-generated by docs/scripts/convert-api-sidebar.mjs from openapi.json
|
||||
sidebar.js
|
||||
sidebar.ts
|
||||
# auto generated
|
||||
requirements/*
|
||||
# vendorized
|
||||
|
||||
@@ -31,8 +31,9 @@ You are currently in the `/docs` subdirectory of the Apache Superset repository.
|
||||
├── superset-frontend/ # React/TypeScript frontend
|
||||
└── docs/ # Documentation site (YOU ARE HERE)
|
||||
├── docs/ # Main documentation content
|
||||
├── developer_portal/ # Developer guides (currently disabled)
|
||||
├── components/ # Component playground (currently disabled)
|
||||
├── admin_docs/ # Admin-focused guides
|
||||
├── developer_docs/ # Developer guides
|
||||
├── components/ # Component playground
|
||||
└── docusaurus.config.ts # Site configuration
|
||||
```
|
||||
|
||||
@@ -46,12 +47,19 @@ yarn build # Build production site
|
||||
yarn serve # Serve built site locally
|
||||
|
||||
# Version Management (USE THESE, NOT docusaurus commands)
|
||||
# The add scripts auto-run `generate:smart` so auto-gen content (database
|
||||
# pages, API reference, component pages) is fresh before snapshotting.
|
||||
# For maximum-detail databases.json, drop the `database-diagnostics`
|
||||
# artifact from Python-Integration CI at src/data/databases.json before
|
||||
# cutting. See README.md "Before You Cut".
|
||||
yarn version:add:docs <version> # Add new docs version
|
||||
yarn version:add:developer_portal <version> # Add developer portal version
|
||||
yarn version:add:admin_docs <version> # Add admin docs version
|
||||
yarn version:add:developer_docs <version> # Add developer docs version
|
||||
yarn version:add:components <version> # Add components version
|
||||
yarn version:remove:docs <version> # Remove docs version
|
||||
yarn version:remove:developer_portal <version> # Remove developer portal version
|
||||
yarn version:remove:components <version> # Remove components version
|
||||
yarn version:remove:admin_docs <version> # Remove admin docs version
|
||||
yarn version:remove:developer_docs <version> # Remove developer docs version
|
||||
yarn version:remove:components <version> # Remove components version
|
||||
|
||||
# Quality Checks
|
||||
yarn typecheck # TypeScript validation
|
||||
@@ -95,15 +103,14 @@ docs/
|
||||
└── [security guides]
|
||||
```
|
||||
|
||||
### Developer Portal (`/developer_portal`) - Currently Disabled
|
||||
When enabled, contains developer-focused content:
|
||||
- API documentation
|
||||
- Architecture guides
|
||||
- CLI tools
|
||||
- Code examples
|
||||
### Admin Docs (`/admin_docs`)
|
||||
Admin-focused content: installation, configuration, security.
|
||||
|
||||
### Component Playground (`/components`) - Currently Disabled
|
||||
When enabled, provides interactive component examples for UI development.
|
||||
### Developer Docs (`/developer_docs`)
|
||||
Developer-focused content: API documentation, architecture guides, CLI tools, code examples.
|
||||
|
||||
### Component Playground (`/components`)
|
||||
Interactive component examples for UI development.
|
||||
|
||||
## 📝 Documentation Standards
|
||||
|
||||
|
||||
@@ -37,23 +37,45 @@ Each section maintains its own version history and can be versioned independentl
|
||||
|
||||
To create a new version for any section, use the Docusaurus version command with the appropriate plugin ID or use our automated scripts:
|
||||
|
||||
#### Before You Cut
|
||||
|
||||
The cut snapshots whatever's on disk into a frozen historical version, including auto-generated content (database pages from `superset/db_engine_specs/`, API reference from `static/resources/openapi.json`, component pages from Storybook stories). The cut script refreshes these via `generate:smart` before snapshotting, but the **`databases.json` diagnostics file** needs special care to capture full detail:
|
||||
|
||||
1. **Canonical release cut**: download the `database-diagnostics` artifact from a green `Python-Integration` run on master, place it at `docs/src/data/databases.json`, then run the cut script with `--skip-generate` to preserve it. This is what the production deploy uses and includes full Flask-context diagnostics (driver versions, feature support matrix, etc.).
|
||||
2. **Local dev cut**: just run the script normally. `generate:smart` will regenerate `databases.json` using your local Flask environment — accurate to whatever drivers/extras you have installed, but typically less complete than the CI artifact.
|
||||
3. **No Flask available**: also fine — the database generator falls back to AST parsing of engine spec files. The MDX pages are still correct; only the diagnostics JSON is leaner.
|
||||
|
||||
Also: confirm `master` CI is green, and that your local checkout matches the SHA you intend to cut from.
|
||||
|
||||
#### Using Automated Scripts (Required)
|
||||
|
||||
**⚠️ Important:** Always use these custom commands instead of the native Docusaurus commands. These scripts ensure that both the Docusaurus versioning system AND the `versions-config.json` file are updated correctly.
|
||||
**⚠️ Important:** Always use these custom commands instead of the native Docusaurus commands. These scripts ensure that both the Docusaurus versioning system AND the `versions-config.json` file are updated correctly, AND that auto-generated content is refreshed before snapshotting.
|
||||
|
||||
```bash
|
||||
# Main Documentation
|
||||
yarn version:add:docs 1.2.0
|
||||
|
||||
# Developer Portal
|
||||
yarn version:add:developer_portal 1.2.0
|
||||
# Admin Docs
|
||||
yarn version:add:admin_docs 1.2.0
|
||||
|
||||
# Component Playground (when enabled)
|
||||
# Developer Docs
|
||||
yarn version:add:developer_docs 1.2.0
|
||||
|
||||
# Component Playground
|
||||
yarn version:add:components 1.2.0
|
||||
```
|
||||
|
||||
What the script does:
|
||||
1. Refreshes auto-generated content via `generate:smart` (database pages, API reference, component pages).
|
||||
2. Calls `yarn docusaurus docs:version` (or the per-section equivalent) to snapshot the section.
|
||||
3. Freezes any data-file imports (`@site/static/*.json`, `../../data/*.json`) into a snapshot-local `_versioned_data/` dir so the historical version doesn't silently mutate when the source files change.
|
||||
4. Adjusts relative import paths (`../../src/...` → `../../../src/...`) for files now one directory deeper.
|
||||
5. Updates `versions-config.json` and `<section>_versions.json`.
|
||||
|
||||
**Do NOT use** the native Docusaurus commands directly (`yarn docusaurus docs:version`), as they will:
|
||||
- ❌ Create version files but NOT update `versions-config.json`
|
||||
- ❌ Skip auto-gen refresh, freezing whatever was on disk
|
||||
- ❌ Skip data-import freezing, leaving the snapshot pointed at live data
|
||||
- ❌ Cause versions to not appear in dropdown menus
|
||||
- ❌ Require manual fixes to synchronize the configuration
|
||||
|
||||
@@ -91,8 +113,11 @@ If creating versions manually, you'll need to:
|
||||
# Main Documentation
|
||||
yarn version:remove:docs 1.0.0
|
||||
|
||||
# Developer Portal
|
||||
yarn version:remove:developer_portal 1.0.0
|
||||
# Admin Docs
|
||||
yarn version:remove:admin_docs 1.0.0
|
||||
|
||||
# Developer Docs
|
||||
yarn version:remove:developer_docs 1.0.0
|
||||
|
||||
# Component Playground
|
||||
yarn version:remove:components 1.0.0
|
||||
@@ -103,17 +128,20 @@ To manually remove a version:
|
||||
|
||||
1. **Delete the version folder** from the appropriate location:
|
||||
- Main docs: `versioned_docs/version-X.X.X/` (no prefix for main)
|
||||
- Developer Portal: `developer_portal_versioned_docs/version-X.X.X/`
|
||||
- Admin Docs: `admin_docs_versioned_docs/version-X.X.X/`
|
||||
- Developer Docs: `developer_docs_versioned_docs/version-X.X.X/`
|
||||
- Components: `components_versioned_docs/version-X.X.X/`
|
||||
|
||||
2. **Delete the version metadata file**:
|
||||
- Main docs: `versioned_sidebars/version-X.X.X-sidebars.json` (no prefix)
|
||||
- Developer Portal: `developer_portal_versioned_sidebars/version-X.X.X-sidebars.json`
|
||||
- Admin Docs: `admin_docs_versioned_sidebars/version-X.X.X-sidebars.json`
|
||||
- Developer Docs: `developer_docs_versioned_sidebars/version-X.X.X-sidebars.json`
|
||||
- Components: `components_versioned_sidebars/version-X.X.X-sidebars.json`
|
||||
|
||||
3. **Update the versions list file**:
|
||||
- Main docs: `versions.json`
|
||||
- Developer Portal: `developer_portal_versions.json`
|
||||
- Admin Docs: `admin_docs_versions.json`
|
||||
- Developer Docs: `developer_docs_versions.json`
|
||||
- Components: `components_versions.json`
|
||||
|
||||
4. **Update configuration**:
|
||||
@@ -145,12 +173,12 @@ docs: {
|
||||
}
|
||||
```
|
||||
|
||||
#### Developer Portal & Components (custom plugins)
|
||||
#### Developer Docs & Components (custom plugins)
|
||||
```typescript
|
||||
{
|
||||
id: 'developer_portal',
|
||||
path: 'developer_portal',
|
||||
routeBasePath: 'developer_portal',
|
||||
id: 'developer_docs',
|
||||
path: 'developer_docs',
|
||||
routeBasePath: 'developer-docs',
|
||||
includeCurrentVersion: true,
|
||||
lastVersion: '1.1.0', // Default version
|
||||
onlyIncludeVersions: ['current', '1.1.0', '1.0.0'],
|
||||
@@ -194,7 +222,7 @@ For other issues:
|
||||
|
||||
#### Broken Links in Versioned Documentation
|
||||
When creating a new version, links in the documentation are preserved as-is. Common issues:
|
||||
- **Cross-section links**: Links between sections (e.g., from developer_portal to docs) need to be version-aware
|
||||
- **Cross-section links**: Links between sections (e.g., from developer_docs to docs) need to be version-aware
|
||||
- **Absolute vs relative paths**: Use relative paths within the same section
|
||||
- **Version-specific URLs**: Update hardcoded URLs to use version variables
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -33,10 +33,12 @@
|
||||
"version:add": "node scripts/manage-versions.mjs add",
|
||||
"version:remove": "node scripts/manage-versions.mjs remove",
|
||||
"version:add:docs": "node scripts/manage-versions.mjs add docs",
|
||||
"version:add:developer_portal": "node scripts/manage-versions.mjs add developer_portal",
|
||||
"version:add:admin_docs": "node scripts/manage-versions.mjs add admin_docs",
|
||||
"version:add:developer_docs": "node scripts/manage-versions.mjs add developer_docs",
|
||||
"version:add:components": "node scripts/manage-versions.mjs add components",
|
||||
"version:remove:docs": "node scripts/manage-versions.mjs remove docs",
|
||||
"version:remove:developer_portal": "node scripts/manage-versions.mjs remove developer_portal",
|
||||
"version:remove:admin_docs": "node scripts/manage-versions.mjs remove admin_docs",
|
||||
"version:remove:developer_docs": "node scripts/manage-versions.mjs remove developer_docs",
|
||||
"version:remove:components": "node scripts/manage-versions.mjs remove components"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -30,9 +30,11 @@ const __dirname = path.dirname(__filename);
|
||||
const CONFIG_FILE = path.join(__dirname, '..', 'versions-config.json');
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const rawArgs = process.argv.slice(2);
|
||||
const skipGenerate = rawArgs.includes('--skip-generate');
|
||||
const args = rawArgs.filter((a) => a !== '--skip-generate');
|
||||
const command = args[0]; // 'add' or 'remove'
|
||||
const section = args[1]; // 'docs', 'developer_portal', or 'components'
|
||||
const section = args[1]; // 'docs', 'admin_docs', 'developer_docs', or 'components'
|
||||
const version = args[2]; // version string like '1.2.0'
|
||||
|
||||
function loadConfig() {
|
||||
@@ -43,36 +45,158 @@ function saveConfig(config) {
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
|
||||
}
|
||||
|
||||
function fixVersionedImports(version) {
|
||||
const versionedDocsPath = path.join(__dirname, '..', 'versioned_docs', `version-${version}`);
|
||||
function freezeDataImports(section, version) {
|
||||
// MDX files can `import` JSON/YAML data from outside the section, either
|
||||
// via escaping relative paths (e.g. country-map-tools.mdx imports
|
||||
// `../../data/countries.json`) or via the `@site/` alias (e.g.
|
||||
// feature-flags.mdx imports `@site/static/feature-flags.json`). Without
|
||||
// intervention the snapshot keeps reading the live file, so the
|
||||
// historical version's content silently changes whenever the data file
|
||||
// is updated. Copy each escaping data import into a snapshot-local
|
||||
// `_versioned_data/` dir and rewrite the import to point there.
|
||||
const sectionRoot = section === 'docs'
|
||||
? path.join(__dirname, '..', 'docs')
|
||||
: path.join(__dirname, '..', section);
|
||||
const docsRoot = path.join(__dirname, '..');
|
||||
const versionedDocsDir = section === 'docs'
|
||||
? `versioned_docs/version-${version}`
|
||||
: `${section}_versioned_docs/version-${version}`;
|
||||
const versionedDocsPath = path.join(__dirname, '..', versionedDocsDir);
|
||||
const frozenDataDir = path.join(versionedDocsPath, '_versioned_data');
|
||||
|
||||
// Files that need import path fixes
|
||||
const filesToFix = [
|
||||
'contributing/resources.mdx',
|
||||
'configuration/country-map-tools.mdx'
|
||||
];
|
||||
if (!fs.existsSync(versionedDocsPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` Fixing relative imports in versioned docs...`);
|
||||
console.log(` Freezing data imports in ${versionedDocsDir}...`);
|
||||
|
||||
filesToFix.forEach(filePath => {
|
||||
const fullPath = path.join(versionedDocsPath, filePath);
|
||||
if (fs.existsSync(fullPath)) {
|
||||
let content = fs.readFileSync(fullPath, 'utf8');
|
||||
// Matches data file imports in two flavors:
|
||||
// `from '../../foo/bar.json'` (relative, must escape one or more dirs)
|
||||
// `from '@site/static/foo.json'` (Docusaurus site-root alias)
|
||||
const dataImportRe = /(from\s+['"])((?:\.\.\/)+|@site\/)([^'"\s]+\.(?:json|ya?ml))(['"])/g;
|
||||
|
||||
// Fix imports that go up two directories to go up three instead
|
||||
content = content.replace(
|
||||
/from ['"]\.\.\/\.\.\/src\//g,
|
||||
"from '../../../src/"
|
||||
);
|
||||
content = content.replace(
|
||||
/from ['"]\.\.\/\.\.\/data\//g,
|
||||
"from '../../../data/"
|
||||
);
|
||||
|
||||
fs.writeFileSync(fullPath, content);
|
||||
console.log(` Fixed imports in ${filePath}`);
|
||||
function freezeOne(fullPath, depth, prefix, pathSpec, importPath, suffix) {
|
||||
let resolvedSource;
|
||||
if (pathSpec === '@site/') {
|
||||
// `@site/...` always resolves relative to the docs root.
|
||||
resolvedSource = path.join(docsRoot, importPath);
|
||||
} else {
|
||||
// Relative path — must escape the file's depth within the section
|
||||
// to point at content outside the section. Imports that stay inside
|
||||
// are copied wholesale by Docusaurus, so we leave them alone.
|
||||
const upCount = pathSpec.match(/\.\.\//g).length;
|
||||
if (upCount <= depth) return null;
|
||||
const relativeFromVersioned = path.relative(versionedDocsPath, fullPath);
|
||||
const originalDir = path.dirname(path.join(sectionRoot, relativeFromVersioned));
|
||||
resolvedSource = path.resolve(originalDir, pathSpec + importPath);
|
||||
}
|
||||
});
|
||||
// Skip imports that land inside the section root — those get copied
|
||||
// with the section snapshot already.
|
||||
const relFromSection = path.relative(sectionRoot, resolvedSource);
|
||||
if (!relFromSection.startsWith('..')) return null;
|
||||
const relFromDocsRoot = path.relative(docsRoot, resolvedSource);
|
||||
if (relFromDocsRoot.startsWith('..') || !fs.existsSync(resolvedSource)) {
|
||||
return null;
|
||||
}
|
||||
const destPath = path.join(frozenDataDir, relFromDocsRoot);
|
||||
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
||||
fs.copyFileSync(resolvedSource, destPath);
|
||||
const rewritten = path
|
||||
.relative(path.dirname(fullPath), destPath)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
const finalImport = rewritten.startsWith('.') ? rewritten : `./${rewritten}`;
|
||||
return `${prefix}${finalImport}${suffix}`;
|
||||
}
|
||||
|
||||
function walk(dir, depth) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name.startsWith('_')) continue;
|
||||
walk(fullPath, depth + 1);
|
||||
} else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) {
|
||||
const original = fs.readFileSync(fullPath, 'utf8');
|
||||
let inFence = false;
|
||||
let mutated = false;
|
||||
const updated = original.split('\n').map(line => {
|
||||
if (/^\s*(```|~~~)/.test(line)) {
|
||||
inFence = !inFence;
|
||||
return line;
|
||||
}
|
||||
if (inFence) return line;
|
||||
return line.replace(dataImportRe, (match, prefix, pathSpec, importPath, suffix) => {
|
||||
const rewritten = freezeOne(fullPath, depth, prefix, pathSpec, importPath, suffix);
|
||||
if (rewritten === null) return match;
|
||||
mutated = true;
|
||||
return rewritten;
|
||||
});
|
||||
}).join('\n');
|
||||
if (mutated) {
|
||||
fs.writeFileSync(fullPath, updated);
|
||||
const rel = path.relative(versionedDocsPath, fullPath);
|
||||
console.log(` Froze data imports in ${rel}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(versionedDocsPath, 0);
|
||||
}
|
||||
|
||||
function fixVersionedImports(section, version) {
|
||||
// Versioned content lands one directory deeper than the source content,
|
||||
// so any `../../src/` or `../../data/` imports in .md/.mdx files need
|
||||
// an extra `../` to keep reaching docs/src and docs/data.
|
||||
const versionedDocsDir = section === 'docs'
|
||||
? `versioned_docs/version-${version}`
|
||||
: `${section}_versioned_docs/version-${version}`;
|
||||
const versionedDocsPath = path.join(__dirname, '..', versionedDocsDir);
|
||||
|
||||
if (!fs.existsSync(versionedDocsPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` Fixing relative imports in ${versionedDocsDir}...`);
|
||||
|
||||
// Imports whose `../` count exceeds the file's depth within the section
|
||||
// escape the section root, so they need one extra `../` once the file
|
||||
// lives one level deeper inside the snapshot dir. Imports that stay
|
||||
// inside the section are unaffected (the section copies wholesale).
|
||||
function walk(dir, depth) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walk(fullPath, depth + 1);
|
||||
} else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) {
|
||||
const original = fs.readFileSync(fullPath, 'utf8');
|
||||
// Track fenced code blocks so we don't rewrite import samples inside
|
||||
// ```ts / ```js (etc.) blocks that are documentation, not real imports.
|
||||
let inFence = false;
|
||||
const updated = original.split('\n').map(line => {
|
||||
if (/^\s*(```|~~~)/.test(line)) {
|
||||
inFence = !inFence;
|
||||
return line;
|
||||
}
|
||||
if (inFence) return line;
|
||||
return line.replace(
|
||||
/(from\s+['"])((?:\.\.\/)+)/g,
|
||||
(match, prefix, dots) => {
|
||||
const upCount = dots.match(/\.\.\//g).length;
|
||||
return upCount > depth ? `${prefix}../${dots}` : match;
|
||||
},
|
||||
);
|
||||
}).join('\n');
|
||||
if (updated !== original) {
|
||||
fs.writeFileSync(fullPath, updated);
|
||||
const rel = path.relative(versionedDocsPath, fullPath);
|
||||
console.log(` Fixed imports in ${rel}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(versionedDocsPath, 0);
|
||||
}
|
||||
|
||||
function addVersion(section, version) {
|
||||
@@ -91,6 +215,28 @@ function addVersion(section, version) {
|
||||
|
||||
console.log(`Creating version ${version} for ${section}...`);
|
||||
|
||||
// Refresh auto-generated content (database pages, API reference,
|
||||
// component playground) so the snapshot captures the current state of
|
||||
// master rather than whatever happened to be on disk. `generate:smart`
|
||||
// hashes its inputs and skips unchanged generators, so this is cheap
|
||||
// when the dev already has fresh output.
|
||||
//
|
||||
// Use --skip-generate if you've placed a CI-artifact databases.json
|
||||
// (the `database-diagnostics` artifact from Python-Integration) and
|
||||
// want to preserve it instead of letting the local env regenerate it.
|
||||
// See docs/README.md "Before You Cut" for the canonical release flow.
|
||||
if (skipGenerate) {
|
||||
console.log(` Skipping auto-gen refresh (--skip-generate set)`);
|
||||
} else {
|
||||
console.log(` Refreshing auto-generated docs...`);
|
||||
try {
|
||||
execSync('yarn run generate:smart', { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
console.error(`Failed to refresh auto-generated docs: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run Docusaurus version command
|
||||
const docusaurusCommand = section === 'docs'
|
||||
? `yarn docusaurus docs:version ${version}`
|
||||
@@ -103,10 +249,12 @@ function addVersion(section, version) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Fix relative imports in versioned docs (for main docs section only)
|
||||
if (section === 'docs') {
|
||||
fixVersionedImports(version);
|
||||
}
|
||||
// Freeze data imports BEFORE adjusting paths, so the depth-aware rewriter
|
||||
// doesn't process the now-local imports we just rewrote.
|
||||
freezeDataImports(section, version);
|
||||
|
||||
// Fix relative imports in versioned content
|
||||
fixVersionedImports(section, version);
|
||||
|
||||
// Update config
|
||||
// Add to onlyIncludeVersions array (after 'current')
|
||||
@@ -121,10 +269,15 @@ function addVersion(section, version) {
|
||||
banner: 'none'
|
||||
};
|
||||
|
||||
// Optionally update lastVersion if this is the first non-current version
|
||||
if (config[section].onlyIncludeVersions.length === 2) {
|
||||
config[section].lastVersion = version;
|
||||
}
|
||||
// Note: we deliberately do NOT auto-bump `lastVersion` to the new
|
||||
// version. Superset's docs site keeps `lastVersion: 'current'` so
|
||||
// the canonical URLs (`/user-docs/...`, `/admin-docs/...`,
|
||||
// `/developer-docs/...`, `/components/...`) always render master
|
||||
// content; cut versions are accessed only via their explicit version
|
||||
// segment. (`/docs/...` paths are legacy and handled via per-page
|
||||
// redirects in docusaurus.config.ts — not a current canonical
|
||||
// form.) If you want a different policy, edit versions-config.json
|
||||
// after cutting.
|
||||
|
||||
saveConfig(config);
|
||||
console.log(`✅ Version ${version} added successfully to ${section}`);
|
||||
@@ -185,8 +338,17 @@ function removeVersion(section, version) {
|
||||
const versionIndex = versions.indexOf(version);
|
||||
if (versionIndex > -1) {
|
||||
versions.splice(versionIndex, 1);
|
||||
fs.writeFileSync(versionsJsonPath, JSON.stringify(versions, null, 2) + '\n');
|
||||
console.log(` Updated ${versionsJsonFile}`);
|
||||
if (versions.length === 0) {
|
||||
// Sections with no versions shouldn't carry an empty versions file
|
||||
// on disk — Docusaurus doesn't require it, and an empty `[]` file
|
||||
// gets picked up by `docusaurus version` and snapshotted into the
|
||||
// next cut.
|
||||
fs.unlinkSync(versionsJsonPath);
|
||||
console.log(` Removed empty ${versionsJsonFile}`);
|
||||
} else {
|
||||
fs.writeFileSync(versionsJsonPath, JSON.stringify(versions, null, 2) + '\n');
|
||||
console.log(` Updated ${versionsJsonFile}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,17 +373,20 @@ function removeVersion(section, version) {
|
||||
function printUsage() {
|
||||
console.log(`
|
||||
Usage:
|
||||
node scripts/manage-versions.js add <section> <version>
|
||||
node scripts/manage-versions.js remove <section> <version>
|
||||
node scripts/manage-versions.mjs add <section> <version> [--skip-generate]
|
||||
node scripts/manage-versions.mjs remove <section> <version>
|
||||
|
||||
Where:
|
||||
- section: 'docs', 'developer_portal', or 'components'
|
||||
- section: 'docs', 'developer_docs', 'admin_docs', or 'components'
|
||||
- version: version string (e.g., '1.2.0', '2.0.0')
|
||||
- --skip-generate: skip refreshing auto-generated docs before snapshotting
|
||||
(use when you've already placed a fresh databases.json
|
||||
from CI and want to preserve it)
|
||||
|
||||
Examples:
|
||||
node scripts/manage-versions.js add docs 2.0.0
|
||||
node scripts/manage-versions.js add developer_portal 1.3.0
|
||||
node scripts/manage-versions.js remove components 1.0.0
|
||||
node scripts/manage-versions.mjs add docs 2.0.0
|
||||
node scripts/manage-versions.mjs add developer_docs 1.3.0
|
||||
node scripts/manage-versions.mjs remove components 1.0.0
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,19 +30,30 @@ import { DownOutlined } from '@ant-design/icons';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
// Map each versioned plugin id to the URL prefix it actually serves
|
||||
// content from. Three of the four routeBasePath values differ from
|
||||
// their pluginId — the default preset-classic docs plugin lives at
|
||||
// `/user-docs`, and admin_docs / developer_docs use hyphens in their
|
||||
// URLs even though the plugin ids use underscores. Without this map
|
||||
// the basePath derivation below would mis-split the pathname for
|
||||
// those sections and the version dropdown would jump to the section
|
||||
// root instead of preserving the current page.
|
||||
//
|
||||
// Keep in sync with the `routeBasePath` values in docusaurus.config.ts.
|
||||
const PLUGIN_ID_TO_BASE_PATH = {
|
||||
default: '/user-docs',
|
||||
components: '/components',
|
||||
admin_docs: '/admin-docs',
|
||||
developer_docs: '/developer-docs',
|
||||
};
|
||||
|
||||
export default function DocVersionBadge() {
|
||||
const activePlugin = useActivePlugin();
|
||||
const { pathname } = useLocation();
|
||||
const pluginId = activePlugin?.pluginId;
|
||||
const [versionedPath, setVersionedPath] = React.useState('');
|
||||
|
||||
// Show version selector for all versioned sections
|
||||
const isVersioned = [
|
||||
'default', // main docs
|
||||
'components',
|
||||
'tutorials',
|
||||
'developer_portal',
|
||||
].includes(pluginId);
|
||||
const isVersioned = pluginId && pluginId in PLUGIN_ID_TO_BASE_PATH;
|
||||
|
||||
const { preferredVersion } = useDocsPreferredVersion(pluginId);
|
||||
const versions = useVersions(pluginId);
|
||||
@@ -53,7 +64,8 @@ export default function DocVersionBadge() {
|
||||
if (!pathname || !version || !pluginId) return;
|
||||
|
||||
let relativePath = '';
|
||||
const basePath = pluginId === 'default' ? '/docs' : `/${pluginId}`;
|
||||
const basePath = PLUGIN_ID_TO_BASE_PATH[pluginId];
|
||||
if (!basePath) return;
|
||||
|
||||
// Handle different version path patterns
|
||||
if (pathname.includes(basePath)) {
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import DocVersionBanner from '@theme-original/DocVersionBanner';
|
||||
import {
|
||||
useActivePlugin,
|
||||
useDocsVersion,
|
||||
useVersions,
|
||||
} from '@docusaurus/plugin-content-docs/client';
|
||||
import { useLocation } from '@docusaurus/router';
|
||||
import { useDocsPreferredVersion } from '@docusaurus/theme-common';
|
||||
import { Dropdown } from 'antd';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export default function DocVersionBannerWrapper(props) {
|
||||
const activePlugin = useActivePlugin();
|
||||
const { pathname } = useLocation();
|
||||
const pluginId = activePlugin?.pluginId;
|
||||
const [versionedPath, setVersionedPath] = useState('');
|
||||
|
||||
// Only show version selector for tutorials
|
||||
// Main docs, components, and developer_portal use the DocVersionBadge component instead
|
||||
const isVersioned = pluginId && ['tutorials'].includes(pluginId);
|
||||
|
||||
const { preferredVersion } = useDocsPreferredVersion(pluginId);
|
||||
const versions = useVersions(pluginId);
|
||||
const version = useDocsVersion();
|
||||
|
||||
// Early return if required data is not available
|
||||
if (!isVersioned || !versions || !version) {
|
||||
return <DocVersionBanner {...props} />;
|
||||
}
|
||||
|
||||
// Extract the current page path relative to the version
|
||||
useEffect(() => {
|
||||
if (!pathname || !version || !pluginId) return;
|
||||
|
||||
let relativePath = '';
|
||||
|
||||
// Handle different version path patterns
|
||||
if (pathname.includes(`/${pluginId}/`)) {
|
||||
// Extract the part after the version
|
||||
// Example: /components/1.1.0/ui-components/button -> /ui-components/button
|
||||
const parts = pathname.split(`/${pluginId}/`);
|
||||
if (parts.length > 1) {
|
||||
const afterPluginId = parts[1];
|
||||
// Find where the version part ends
|
||||
const versionParts = afterPluginId.split('/');
|
||||
if (versionParts.length > 1) {
|
||||
// Remove the version part and join the rest
|
||||
relativePath = '/' + versionParts.slice(1).join('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setVersionedPath(relativePath);
|
||||
}, [pathname, version, pluginId]);
|
||||
|
||||
// Create dropdown items for version selection
|
||||
const items = versions.map(v => {
|
||||
// Construct the URL for this version, preserving the current page
|
||||
// v.path is the version-specific path like "1.0.0" or "next"
|
||||
let versionUrl = v.path;
|
||||
|
||||
if (versionedPath) {
|
||||
// Construct the full URL with the version and the current page path
|
||||
versionUrl = v.path + versionedPath;
|
||||
}
|
||||
|
||||
return {
|
||||
key: v.name,
|
||||
label: (
|
||||
<a href={versionUrl}>
|
||||
{v.label}
|
||||
{v.name === version.name && ' (current)'}
|
||||
{v.name === preferredVersion?.name && ' (preferred)'}
|
||||
</a>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocVersionBanner {...props} />
|
||||
{isVersioned && (
|
||||
<div className={styles.versionBanner}>
|
||||
<div className={styles.versionContainer}>
|
||||
<span className={styles.versionLabel}>Version:</span>
|
||||
<Dropdown menu={{ items }} trigger={['click']}>
|
||||
<a
|
||||
onClick={e => e.preventDefault()}
|
||||
className={styles.versionSelector}
|
||||
>
|
||||
{version.label} <DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
[
|
||||
"1.0.0"
|
||||
]
|
||||
@@ -20,7 +20,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
# Define patterns for each group of files you're interested in
|
||||
@@ -111,7 +111,7 @@ def main(event_type: str, sha: str, repo: str) -> None:
|
||||
"""Main function to check for file changes based on event context."""
|
||||
print("SHA:", sha)
|
||||
print("EVENT_TYPE", event_type)
|
||||
files = []
|
||||
files: Optional[List[str]] = []
|
||||
if event_type == "pull_request":
|
||||
pr_number = os.getenv("GITHUB_REF", "").split("/")[-2]
|
||||
if is_int(pr_number):
|
||||
@@ -124,11 +124,15 @@ def main(event_type: str, sha: str, repo: str) -> None:
|
||||
print("Files touched since previous commit:")
|
||||
print_files(files)
|
||||
|
||||
elif event_type == "workflow_dispatch":
|
||||
print("Workflow dispatched, assuming all changed")
|
||||
elif event_type in ("workflow_dispatch", "schedule"):
|
||||
# Manual or cron-triggered runs aren't tied to a specific diff, so
|
||||
# treat every group as changed. `files = None` makes the loop below
|
||||
# short-circuit to True for every group via `files is None or ...`.
|
||||
print(f"{event_type} run, assuming all changed")
|
||||
files = None
|
||||
|
||||
else:
|
||||
raise ValueError("Unsupported event type")
|
||||
raise ValueError(f"Unsupported event type: {event_type}")
|
||||
|
||||
changes_detected = {}
|
||||
for group, regex_patterns in PATTERNS.items():
|
||||
@@ -144,7 +148,7 @@ def main(event_type: str, sha: str, repo: str) -> None:
|
||||
# NOTE: as noted above, we assume that if 100 files are touched, we should
|
||||
# trigger all checks. This is a workaround for the GitHub API limit of 100
|
||||
# files. Using >= 99 because off-by-one errors are not uncommon
|
||||
if changed or len(files) >= 99:
|
||||
if changed or (files is not None and len(files) >= 99):
|
||||
print(f"{check}=true", file=f)
|
||||
print(f"Triggering group: {check}")
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
[project]
|
||||
name = "apache-superset-core"
|
||||
version = "0.1.0rc3"
|
||||
version = "0.1.0"
|
||||
description = "Core Python package for building Apache Superset backend extensions and integrations"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
|
||||
545
superset-embedded-sdk/package-lock.json
generated
545
superset-embedded-sdk/package-lock.json
generated
@@ -71,14 +71,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.24.7",
|
||||
"picocolors": "^1.0.0"
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -126,16 +126,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz",
|
||||
"integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==",
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.6",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -242,6 +242,15 @@
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz",
|
||||
@@ -257,30 +266,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
|
||||
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
|
||||
"integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
||||
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.24.7",
|
||||
"@babel/helper-simple-access": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/traverse": "^7.25.2"
|
||||
"@babel/helper-module-imports": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -303,11 +309,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
|
||||
"integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
||||
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -377,21 +382,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -435,30 +438,13 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
|
||||
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
|
||||
"integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.6"
|
||||
"@babel/types": "^7.29.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -1262,16 +1248,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz",
|
||||
"integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==",
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
|
||||
"integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.25.0",
|
||||
"@babel/helper-plugin-utils": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/traverse": "^7.25.0"
|
||||
"@babel/helper-module-transforms": "^7.28.6",
|
||||
"@babel/helper-plugin-utils": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.29.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1858,49 +1843,45 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
|
||||
"integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/parser": "^7.25.0",
|
||||
"@babel/types": "^7.25.0"
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz",
|
||||
"integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.25.6",
|
||||
"@babel/parser": "^7.25.6",
|
||||
"@babel/template": "^7.25.0",
|
||||
"@babel/types": "^7.25.6",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
|
||||
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -2659,17 +2640,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
@@ -2681,15 +2658,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
@@ -2701,15 +2669,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@@ -3199,19 +3167,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
@@ -3617,21 +3572,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/char-regex": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
||||
@@ -3741,23 +3681,6 @@
|
||||
"integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "2.0.16",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
|
||||
@@ -4037,16 +3960,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
@@ -4619,16 +4532,6 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -6635,8 +6538,7 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.2",
|
||||
@@ -6652,15 +6554,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
@@ -7504,19 +7406,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
@@ -7619,15 +7508,6 @@
|
||||
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -8131,13 +8011,14 @@
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.24.7",
|
||||
"picocolors": "^1.0.0"
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
@@ -8170,15 +8051,16 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz",
|
||||
"integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==",
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.25.6",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-annotate-as-pure": {
|
||||
@@ -8252,6 +8134,12 @@
|
||||
"resolve": "^1.14.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz",
|
||||
@@ -8263,25 +8151,24 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
|
||||
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/traverse": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
|
||||
"integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
||||
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.24.7",
|
||||
"@babel/helper-simple-access": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/traverse": "^7.25.2"
|
||||
"@babel/helper-module-imports": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-optimise-call-expression": {
|
||||
@@ -8294,9 +8181,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
|
||||
"integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
||||
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-remap-async-to-generator": {
|
||||
@@ -8342,15 +8229,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
@@ -8380,25 +8267,13 @@
|
||||
"@babel/types": "^7.25.6"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
|
||||
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
|
||||
"integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.25.6"
|
||||
"@babel/types": "^7.29.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
|
||||
@@ -8891,15 +8766,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz",
|
||||
"integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==",
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
|
||||
"integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-transforms": "^7.25.0",
|
||||
"@babel/helper-plugin-utils": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/traverse": "^7.25.0"
|
||||
"@babel/helper-module-transforms": "^7.28.6",
|
||||
"@babel/helper-plugin-utils": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.29.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-modules-umd": {
|
||||
@@ -9282,40 +9157,39 @@
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
|
||||
"integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/parser": "^7.25.0",
|
||||
"@babel/types": "^7.25.0"
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz",
|
||||
"integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.25.6",
|
||||
"@babel/parser": "^7.25.6",
|
||||
"@babel/template": "^7.25.0",
|
||||
"@babel/types": "^7.25.6",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"debug": "^4.3.1"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
|
||||
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
}
|
||||
},
|
||||
"@bcoe/v8-coverage": {
|
||||
@@ -9888,13 +9762,12 @@
|
||||
}
|
||||
},
|
||||
"@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
@@ -9904,12 +9777,6 @@
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/set-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
@@ -9921,15 +9788,15 @@
|
||||
}
|
||||
},
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@@ -10355,15 +10222,6 @@
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
@@ -10649,17 +10507,6 @@
|
||||
"integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"char-regex": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
||||
@@ -10735,21 +10582,6 @@
|
||||
"integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"colorette": {
|
||||
"version": "2.0.16",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
|
||||
@@ -10949,12 +10781,6 @@
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
@@ -11351,12 +11177,6 @@
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true
|
||||
},
|
||||
"html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -12844,9 +12664,9 @@
|
||||
}
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true
|
||||
},
|
||||
"json-parse-even-better-errors": {
|
||||
@@ -13467,15 +13287,6 @@
|
||||
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
@@ -13537,12 +13348,6 @@
|
||||
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
|
||||
"dev": true
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
[project]
|
||||
name = "apache-superset-extensions-cli"
|
||||
version = "0.1.0rc3"
|
||||
version = "0.1.0"
|
||||
description = "Official command-line interface for building, bundling, and managing Apache Superset extensions"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
|
||||
266
superset-frontend/package-lock.json
generated
266
superset-frontend/package-lock.json
generated
@@ -83,7 +83,7 @@
|
||||
"ag-grid-community": "35.2.1",
|
||||
"ag-grid-react": "35.2.1",
|
||||
"antd": "^5.26.0",
|
||||
"chrono-node": "^2.9.0",
|
||||
"chrono-node": "^2.9.1",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^1.1.0",
|
||||
"d3-color": "^3.1.0",
|
||||
@@ -102,15 +102,15 @@
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.6.2",
|
||||
"immer": "^11.1.4",
|
||||
"immer": "^11.1.7",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"json-bigint": "^1.0.0",
|
||||
"json-stringify-pretty-compact": "^2.0.0",
|
||||
"lodash": "^4.18.1",
|
||||
"mapbox-gl": "^3.23.0",
|
||||
"markdown-to-jsx": "^9.7.16",
|
||||
"mapbox-gl": "^3.23.1",
|
||||
"markdown-to-jsx": "^9.8.0",
|
||||
"match-sorter": "^8.3.0",
|
||||
"memoize-one": "^5.2.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
@@ -169,10 +169,10 @@
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
||||
"@babel/plugin-transform-runtime": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.3",
|
||||
"@babel/preset-env": "^7.29.5",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"@babel/register": "^7.23.7",
|
||||
"@babel/register": "^7.29.3",
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"@babel/runtime-corejs3": "^7.29.2",
|
||||
"@babel/types": "^7.28.6",
|
||||
@@ -180,7 +180,7 @@
|
||||
"@emotion/jest": "^11.14.2",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
||||
"@playwright/test": "^1.59.1",
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-actions": "^8.6.18",
|
||||
"@storybook/addon-controls": "^8.6.18",
|
||||
@@ -229,7 +229,7 @@
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"baseline-browser-mapping": "^2.10.24",
|
||||
"baseline-browser-mapping": "^2.10.29",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
@@ -269,7 +269,7 @@
|
||||
"lightningcss": "^1.32.0",
|
||||
"mini-css-extract-plugin": "^2.10.2",
|
||||
"open-cli": "^9.0.0",
|
||||
"oxlint": "^1.62.0",
|
||||
"oxlint": "^1.63.0",
|
||||
"po2json": "^0.4.5",
|
||||
"prettier": "3.8.3",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
@@ -290,7 +290,7 @@
|
||||
"typescript": "5.4.5",
|
||||
"unzipper": "^0.12.3",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"wait-on": "^9.0.6",
|
||||
"wait-on": "^9.0.10",
|
||||
"webpack": "^5.106.2",
|
||||
"webpack-bundle-analyzer": "^5.3.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
@@ -1838,9 +1838,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz",
|
||||
"integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==",
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
|
||||
"integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2409,9 +2409,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/preset-env": {
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.3.tgz",
|
||||
"integrity": "sha512-ySZypNLAIH1ClygLDQzVMoGQRViATnkHkYYV6TcNDz+8+jwZCdsguGvsb3EY5d9wyWyhmF1iSuFM0Yh5XPnqSA==",
|
||||
"version": "7.29.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz",
|
||||
"integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2454,7 +2454,7 @@
|
||||
"@babel/plugin-transform-member-expression-literals": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-amd": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
||||
"@babel/plugin-transform-modules-systemjs": "^7.29.0",
|
||||
"@babel/plugin-transform-modules-systemjs": "^7.29.4",
|
||||
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0",
|
||||
"@babel/plugin-transform-new-target": "^7.27.1",
|
||||
@@ -2575,9 +2575,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/register": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.6.tgz",
|
||||
"integrity": "sha512-pgcbbEl/dWQYb6L6Yew6F94rdwygfuv+vJ/tXfwIOYAfPB6TNWpXUMEtEq3YuTeHRdvMIhvz13bkT9CNaS+wqA==",
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/register/-/register-7.29.3.tgz",
|
||||
"integrity": "sha512-F6C1KpIdoImKQfsD6HSxZ+mS4YY/2Q+JsqrmTC5ApVkTR2rG+nnbpjhWwzA5bDNu8mJjB3AryqDaWFLd4gCbJQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8643,9 +8643,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@oxlint/binding-android-arm-eabi": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.62.0.tgz",
|
||||
"integrity": "sha512-pKsthNECyvJh8lPTICz6VcwVy2jOqdhhsp1rlxCkhgZR47aKvXPmaRWQDv+zlXpRae4qm1MaaTnutkaOk5aofg==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.63.0.tgz",
|
||||
"integrity": "sha512-A9xLtQt7i0OA1PoB/meog6kikXI9CdwEp7ZwQqmgnpKn3G3b1orvTDy8CQ6T7w1HvDrgWGB78PkFKcWgibcTCg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -8660,9 +8660,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-android-arm64": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.62.0.tgz",
|
||||
"integrity": "sha512-b1AUNViByvgmR2xJDubvLIr+dSuu3uraG7bsAoKo+xrpspPvu6RIn6Fhr2JUhobfep3jwUTy18Huco6GkwdvGQ==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.63.0.tgz",
|
||||
"integrity": "sha512-SQo+ZMvdR9l3CxZp5W5gFNxSiDxclY6lOzzNpKYLF8asESpm3Pwumx0gER5T7aHLF1/2BAAtLD3DiDkdgy4V1A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8677,9 +8677,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-darwin-arm64": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.62.0.tgz",
|
||||
"integrity": "sha512-iG+Tvf70UJ6otfwFYIHk36Sjq9cpPP5YLxkoggANNRtzgi3Tj3g8q6Ybqi6AtkU3+yg9QwF7bDCkCS6bbL4PCg==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.63.0.tgz",
|
||||
"integrity": "sha512-6W82XjJDTmMnjg30427l0dufpnyLoq7wEukKdM6/g2VIybRVuQiBVh43EA4b+UxZ3+tLcKm+Or/pXGNgLCEU8g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8694,9 +8694,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-darwin-x64": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.62.0.tgz",
|
||||
"integrity": "sha512-oOWI6YPPr5AJUx+yIDlxmuUbQjS5gZX3OH3QisawYvsZgLiQVvZtR0rPBcJTxLWqt2ClrWg0DlSrlUiG5SQNHg==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.63.0.tgz",
|
||||
"integrity": "sha512-CnWd/YCuVG5W1BYkjJEVbJG11o526O9qAwBEQM+nh8K19CRFUkFdROXCyYkGmroHEYQe4vgQ6+lh3550Lp35Xw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8711,9 +8711,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-freebsd-x64": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.62.0.tgz",
|
||||
"integrity": "sha512-dLP33T7VLCmLVv4cvjkVX+rmkcwNk2UfxmsZPNur/7BQHoQR60zJ7XLiRvNUawlzn0u8ngCa3itjEG73MAMa/w==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.63.0.tgz",
|
||||
"integrity": "sha512-a4eZAqrmtajqcxfdAzC+l7g3PaE3V8hpAYqqeD3fTxLXOMFdK3eNTZrU80n4dDEVm0JXy1aL5PqvqWldBl6zYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8728,9 +8728,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.62.0.tgz",
|
||||
"integrity": "sha512-fl//LWNks6qo9chNY60UDYyIwtp7a5cEx4Y/rHPjaarhuwqx6jtbzEpD5V5AqmdL4a6Y5D8zeXg5HF2Cr0QmSQ==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.63.0.tgz",
|
||||
"integrity": "sha512-tYUtU9TdbU3uXF5D62g5zXJ13iniFGhXQx5vp9cyEjGdbSAY3VdFBSaldYvyoDmgMZ0ZYuwQP1Y4t2Fhejwa0w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -8745,9 +8745,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-arm-musleabihf": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.62.0.tgz",
|
||||
"integrity": "sha512-i5vkAuxvueTODV3J2dL61/TXewDHhMFKvtD156cIsk7GsdfiAu7zW7kY0NJXhKeFHeiMZIh7eFNjkPYH6J47HQ==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.63.0.tgz",
|
||||
"integrity": "sha512-I5r3twFf776UZg9dmRo2xbrKt00tTkORXEVe0ctg4vdTkQvJAjiCHxnbAU2HL1AiJ9cqADA76MAliuilsAWnvg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -8762,9 +8762,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-arm64-gnu": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.62.0.tgz",
|
||||
"integrity": "sha512-QwN19LLuIGuOjEflSeJkZmOTfBdBMlTmW8xbMf8TZhjd//cxVNYQPq75q7oKZBJc6hRx3gY7sX0Egc8cEIFZYg==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.63.0.tgz",
|
||||
"integrity": "sha512-t7ltUkg6FFh4b564QyGir8xIj/QZbXu8FlcRkcyW9+ztr/mfRHlvUOFd95pJCXi9s/L5DrUeWWgpXRS+V+6igQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8779,9 +8779,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-arm64-musl": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.62.0.tgz",
|
||||
"integrity": "sha512-8eCy3FCDuWUM5hWujAv6heMvfZPbcCOU3SdQUAkixZLu5bSzOkNfirJiLGoQFO943xceOKkiQRMQNzH++jM3WA==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.63.0.tgz",
|
||||
"integrity": "sha512-Q5mmZy/XWjuYFUuQyYjOvZ5U/JkKEwnpir6hGxhh6HcdP0V/BKxLo8dqkfF/t7r7AguB17dfS/8+go5AQDRR6g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8796,9 +8796,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-ppc64-gnu": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.62.0.tgz",
|
||||
"integrity": "sha512-NjQ7K7tpTPDe9J+yq8p/s/J0E7lRCkK2uDBDqvT4XIT6f4Z0tlnr59OBg/WcrmVHER1AbrcfyxhGTXgcG8ytWg==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.63.0.tgz",
|
||||
"integrity": "sha512-uBGtuZ0TzLB4x5wVa82HGNvYqY8buwDhyCnCP0R0gkk9szqVsP0MeTtD5HX7EsEuFIt+aYmYxuxeVxs3nTSwtQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -8813,9 +8813,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-riscv64-gnu": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.62.0.tgz",
|
||||
"integrity": "sha512-oKZed9gmSwze29dEt3/Wnsv6l/Ygw/FUst+8Kfpv2SGeS/glEoTGZAMQw37SVyzFV76UTHJN2snGgxK2t2+8ow==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.63.0.tgz",
|
||||
"integrity": "sha512-h4s6FwxE+9MeA181o0dnDwHP32Y/bG8EiB/vrD6Ib+AMt6haigDc/0bUtI/sLmQDBMJnUfaCmtSSrEAqjtEVrA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -8830,9 +8830,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-riscv64-musl": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.62.0.tgz",
|
||||
"integrity": "sha512-gBjBxQ+9lGpAYq+ELqw0w8QXsBnkZclFc7GRX2r0LnEVn3ZTEqeIKpKcGjucmp76Q53bvJD0i4qBWBhcfhSfGA==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.63.0.tgz",
|
||||
"integrity": "sha512-2EaNcCBR8Mcjl5ARtuN3BdEpVkX7KpjSjMGZ/mJMIeaXgTtdz5ytg2VwygMSStA/k0ixfvZFoZOfjDEcouV5vQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -8847,9 +8847,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-s390x-gnu": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.62.0.tgz",
|
||||
"integrity": "sha512-Ew2Kxs9EQ9/mbAIJ2hvocMC0wsOu6YKzStI2eFBDt+Td5O8seVC/oxgRIHqCcl5sf5ratA1nozQBAuv7tphkHg==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.63.0.tgz",
|
||||
"integrity": "sha512-p4hlf/fd7TrYYl3QrWWD0GocqJefwMu3cHQhmi2FvEB/YOvFb5DZN3SMBaPi7B1TM5DeypkEtrVib674q1KKPg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -8864,9 +8864,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-x64-gnu": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.62.0.tgz",
|
||||
"integrity": "sha512-5z25jcAA0gfKyVwz71A0VXgaPlocPoTAxhlv/hgoK6tlCrfoNuw7haWbDHvGMfjXhdic4EqVXGRv5XsTqFnbRQ==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.63.0.tgz",
|
||||
"integrity": "sha512-Vgq9rkRVcPcjbcH+ihYTfpeR7vCXfqpd+z5ItTGc0yYUV59L5ceHYN1iV4H9bKGV7Rn5hkVc7x3mSvHegduENA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8881,9 +8881,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-linux-x64-musl": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.62.0.tgz",
|
||||
"integrity": "sha512-IWpHmMB6ZDllPvqWDkG6AmXrN7JF5e/c4g/0PuURsmlK+vHoYZPB70rr4u1bn3I4LsKCSpqqfveyx6UCOC8wdg==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.63.0.tgz",
|
||||
"integrity": "sha512-3/Lkq/ncooA61rorrC+ZQed1Bc4VpGj+WnGsp58zmxKgvZ2vhreu+dcVyr3mX8NUpq7mfZ4gDDTou/yrF1Pd7A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8898,9 +8898,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-openharmony-arm64": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.62.0.tgz",
|
||||
"integrity": "sha512-fjlSxxrD5pA594vkyikCS9MnPRjQawW6/BLgyTYkO+73wwPlYjkcZ7LSd974l0Q2zkHQmu4DPvJFLYA7o8xrxQ==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.63.0.tgz",
|
||||
"integrity": "sha512-0/EdD/6hDkx5Mfd769PTjvEM8mZ/6Dfukp1dBCL/2PjlIVGEtYdNZyok6ChqYPsT9JcFnlQnUeQzO0/1L/oC9w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8915,9 +8915,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-win32-arm64-msvc": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.62.0.tgz",
|
||||
"integrity": "sha512-EiFXr8loNS0Ul3Gu80+9nr1T8jRmnKocqmHHg16tj5ZqTgUXyb97l2rrspVHdDluyFn9JfR4PoJFdNzw4paHww==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.63.0.tgz",
|
||||
"integrity": "sha512-wb0CUkN8ngwPiRQBjD1Cj0LsHeNvm+Xt6YBHDMtj2DVQVD6Oj8Ri7g6BD+KICf6LaBqZlmzOvy6nF9E/8yyGOg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8932,9 +8932,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-win32-ia32-msvc": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.62.0.tgz",
|
||||
"integrity": "sha512-IgOFvL73li1bFgab+hThXYA0N2Xms2kV2MvZN95cebV+fmrZ9AVui1JSxfeeqRLo3CpPxKZlzhyq4G0cnaAvIw==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.63.0.tgz",
|
||||
"integrity": "sha512-BX5iq+ovdNlVYhSn5qPMUIT0uwAwt2lmEnCnzK+Gkhw4DovIvhGb96OFhV8yzQNUnQxn/xGkOR+X+BLrLDNm8w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -8949,9 +8949,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxlint/binding-win32-x64-msvc": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.62.0.tgz",
|
||||
"integrity": "sha512-6hMpyDWQ2zGA1OXFKBrdYMUveUCO8UJhkO6JdwZPd78xIdHZNhjx+pib+4fC2Cljuhjyl0QwA2F3df/bs4Bp6A==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.63.0.tgz",
|
||||
"integrity": "sha512-QeN/WELOfsXMeYwxvfgQrl6CbVftYUCZsGXHjXQd5Trccm8+i4gmtxaOui4xbJQaiDlviF8F3yLSBloQUeFsfA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -9155,13 +9155,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
|
||||
"integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
|
||||
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.59.1"
|
||||
"playwright": "1.60.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -17358,9 +17358,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.10.24",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz",
|
||||
"integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==",
|
||||
"version": "2.10.29",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz",
|
||||
"integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -18527,9 +18527,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chrono-node": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.9.0.tgz",
|
||||
"integrity": "sha512-glI4YY2Jy6JII5l3d5FN6rcrIbKSQqKPhWsIRYPK2IK8Mm4Q1ZZFdYIaDqglUNf7gNwG+kWIzTn0omzzE0VkvQ==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.9.1.tgz",
|
||||
"integrity": "sha512-nqP8Zp11efCYQIESXPxeDM8ikzN5BDb3Zzou+a66fZq+X2hzKFdsNLQE2/uBAh//BZEMbaMo1eTnagK7hOenAg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
@@ -27028,9 +27028,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "11.1.4",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
|
||||
"integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.7.tgz",
|
||||
"integrity": "sha512-LFVFtAROHcDy1er5UI6nodRFnZ2SgdCXhfNSI+DpObO8N7Pur/muBGsjzH5wpnFHCYhYVQxZskCkV4koQ//3/Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -33680,9 +33680,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mapbox-gl": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.23.0.tgz",
|
||||
"integrity": "sha512-zzjNAaMNvXnAVEUrYpOWmRVEBCIWgDAMLRPvSOoKY3smKvrINFVrRK/1jEpUDbEa7Ppf5Q/nwC6E07tz/i7IKw==",
|
||||
"version": "3.23.1",
|
||||
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.23.1.tgz",
|
||||
"integrity": "sha512-J5M32GunL5KcxvV3ZyLJdr7xtcQ3afAO3VjitLW0v2UVfHjXhq9NO4tkTpn4Dknqwq/pXZG7j1WBfmddLCA26w==",
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"workspaces": [
|
||||
"src/style-spec",
|
||||
@@ -33841,9 +33841,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-to-jsx": {
|
||||
"version": "9.7.16",
|
||||
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-9.7.16.tgz",
|
||||
"integrity": "sha512-+LEgOlYfUEB9i2Oaxasec9H2HytB1+SQcgwFmQiNTKwe8cQ2E9bDNgePGq6ChIycMxtpcEY0g44aQ3uJoMw8eg==",
|
||||
"version": "9.8.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-9.8.0.tgz",
|
||||
"integrity": "sha512-NWL0vDt6BeQgCxc2kK6g2SEPJpQuacu+NJk5j4QT4wlNmbsHdudoAbTGLCIyoPhfMiEqD1OZVBsYK0hvCky/HQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
@@ -38273,9 +38273,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/oxlint": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.62.0.tgz",
|
||||
"integrity": "sha512-1uFkg6HakjsGIpW9wNdeW4/2LOHW9MEkoWjZUTUfQtIHyLIZPYt00w3Sg+H3lH+206FgBPHBbW5dVE5l2ExECQ==",
|
||||
"version": "1.63.0",
|
||||
"resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.63.0.tgz",
|
||||
"integrity": "sha512-9TGXetdjgIHOJ9OiReomP7nnrMkV9HxC1xM2ramJSLQpzxjsAJtQwa4wqkJN2f/uCrqZuJseFuSlWDdvcruveg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -38288,28 +38288,28 @@
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@oxlint/binding-android-arm-eabi": "1.62.0",
|
||||
"@oxlint/binding-android-arm64": "1.62.0",
|
||||
"@oxlint/binding-darwin-arm64": "1.62.0",
|
||||
"@oxlint/binding-darwin-x64": "1.62.0",
|
||||
"@oxlint/binding-freebsd-x64": "1.62.0",
|
||||
"@oxlint/binding-linux-arm-gnueabihf": "1.62.0",
|
||||
"@oxlint/binding-linux-arm-musleabihf": "1.62.0",
|
||||
"@oxlint/binding-linux-arm64-gnu": "1.62.0",
|
||||
"@oxlint/binding-linux-arm64-musl": "1.62.0",
|
||||
"@oxlint/binding-linux-ppc64-gnu": "1.62.0",
|
||||
"@oxlint/binding-linux-riscv64-gnu": "1.62.0",
|
||||
"@oxlint/binding-linux-riscv64-musl": "1.62.0",
|
||||
"@oxlint/binding-linux-s390x-gnu": "1.62.0",
|
||||
"@oxlint/binding-linux-x64-gnu": "1.62.0",
|
||||
"@oxlint/binding-linux-x64-musl": "1.62.0",
|
||||
"@oxlint/binding-openharmony-arm64": "1.62.0",
|
||||
"@oxlint/binding-win32-arm64-msvc": "1.62.0",
|
||||
"@oxlint/binding-win32-ia32-msvc": "1.62.0",
|
||||
"@oxlint/binding-win32-x64-msvc": "1.62.0"
|
||||
"@oxlint/binding-android-arm-eabi": "1.63.0",
|
||||
"@oxlint/binding-android-arm64": "1.63.0",
|
||||
"@oxlint/binding-darwin-arm64": "1.63.0",
|
||||
"@oxlint/binding-darwin-x64": "1.63.0",
|
||||
"@oxlint/binding-freebsd-x64": "1.63.0",
|
||||
"@oxlint/binding-linux-arm-gnueabihf": "1.63.0",
|
||||
"@oxlint/binding-linux-arm-musleabihf": "1.63.0",
|
||||
"@oxlint/binding-linux-arm64-gnu": "1.63.0",
|
||||
"@oxlint/binding-linux-arm64-musl": "1.63.0",
|
||||
"@oxlint/binding-linux-ppc64-gnu": "1.63.0",
|
||||
"@oxlint/binding-linux-riscv64-gnu": "1.63.0",
|
||||
"@oxlint/binding-linux-riscv64-musl": "1.63.0",
|
||||
"@oxlint/binding-linux-s390x-gnu": "1.63.0",
|
||||
"@oxlint/binding-linux-x64-gnu": "1.63.0",
|
||||
"@oxlint/binding-linux-x64-musl": "1.63.0",
|
||||
"@oxlint/binding-openharmony-arm64": "1.63.0",
|
||||
"@oxlint/binding-win32-arm64-msvc": "1.63.0",
|
||||
"@oxlint/binding-win32-ia32-msvc": "1.63.0",
|
||||
"@oxlint/binding-win32-x64-msvc": "1.63.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"oxlint-tsgolint": ">=0.18.0"
|
||||
"oxlint-tsgolint": ">=0.22.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"oxlint-tsgolint": {
|
||||
@@ -39123,13 +39123,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.59.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
|
||||
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.59.1"
|
||||
"playwright-core": "1.60.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -39142,9 +39142,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.59.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
|
||||
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
||||
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -47992,9 +47992,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wait-on": {
|
||||
"version": "9.0.6",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.6.tgz",
|
||||
"integrity": "sha512-KR+Te+NBg6DmPVil4anyIO72mpt/QDHjRo3nVFkwRgb26oweUp3DDW2szO3EeUY4cqafWy4rQuOOeEk4n+7Oeg==",
|
||||
"version": "9.0.10",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.10.tgz",
|
||||
"integrity": "sha512-rCoJEhvMr0X6alHmwc9abbrA5ZrLZFKpFQVKPNFwl2h7DapXOGdmimIHDtLOWhT4PjhZhxFEtZoQgEXbkDWdZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -49733,7 +49733,7 @@
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"lodash-es": "^4.18.1",
|
||||
"yeoman-generator": "^8.1.2",
|
||||
"yeoman-generator": "^8.2.2",
|
||||
"yosay": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -50107,12 +50107,12 @@
|
||||
},
|
||||
"packages/superset-core": {
|
||||
"name": "@apache-superset/core",
|
||||
"version": "0.1.0-rc3",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.28.6",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.3",
|
||||
"@babel/preset-env": "^7.29.5",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
@@ -50892,7 +50892,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@math.gl/web-mercator": "^4.1.0",
|
||||
"mapbox-gl": "^3.23.0",
|
||||
"mapbox-gl": "^3.23.1",
|
||||
"maplibre-gl": "^5.24.0",
|
||||
"react-map-gl": "^8.1.0",
|
||||
"supercluster": "^8.0.1"
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
"ag-grid-community": "35.2.1",
|
||||
"ag-grid-react": "35.2.1",
|
||||
"antd": "^5.26.0",
|
||||
"chrono-node": "^2.9.0",
|
||||
"chrono-node": "^2.9.1",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^1.1.0",
|
||||
"d3-color": "^3.1.0",
|
||||
@@ -183,15 +183,15 @@
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.6.2",
|
||||
"immer": "^11.1.4",
|
||||
"immer": "^11.1.7",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"json-bigint": "^1.0.0",
|
||||
"json-stringify-pretty-compact": "^2.0.0",
|
||||
"lodash": "^4.18.1",
|
||||
"mapbox-gl": "^3.23.0",
|
||||
"markdown-to-jsx": "^9.7.16",
|
||||
"mapbox-gl": "^3.23.1",
|
||||
"markdown-to-jsx": "^9.8.0",
|
||||
"match-sorter": "^8.3.0",
|
||||
"memoize-one": "^5.2.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
@@ -250,10 +250,10 @@
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
||||
"@babel/plugin-transform-runtime": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.3",
|
||||
"@babel/preset-env": "^7.29.5",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"@babel/register": "^7.23.7",
|
||||
"@babel/register": "^7.29.3",
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"@babel/runtime-corejs3": "^7.29.2",
|
||||
"@babel/types": "^7.28.6",
|
||||
@@ -261,7 +261,7 @@
|
||||
"@emotion/jest": "^11.14.2",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
||||
"@playwright/test": "^1.59.1",
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-actions": "^8.6.18",
|
||||
"@storybook/addon-controls": "^8.6.18",
|
||||
@@ -310,7 +310,7 @@
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"baseline-browser-mapping": "^2.10.24",
|
||||
"baseline-browser-mapping": "^2.10.29",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
@@ -350,7 +350,7 @@
|
||||
"lightningcss": "^1.32.0",
|
||||
"mini-css-extract-plugin": "^2.10.2",
|
||||
"open-cli": "^9.0.0",
|
||||
"oxlint": "^1.62.0",
|
||||
"oxlint": "^1.63.0",
|
||||
"po2json": "^0.4.5",
|
||||
"prettier": "3.8.3",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
@@ -371,7 +371,7 @@
|
||||
"typescript": "5.4.5",
|
||||
"unzipper": "^0.12.3",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"wait-on": "^9.0.6",
|
||||
"wait-on": "^9.0.10",
|
||||
"webpack": "^5.106.2",
|
||||
"webpack-bundle-analyzer": "^5.3.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@apache-superset/core",
|
||||
"version": "0.1.0-rc3",
|
||||
"version": "0.1.0",
|
||||
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
|
||||
"sideEffects": false,
|
||||
"main": "lib/index.js",
|
||||
@@ -75,7 +75,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.28.6",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.3",
|
||||
"@babel/preset-env": "^7.29.5",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"typescript": "^5.0.0",
|
||||
|
||||
@@ -17,59 +17,109 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Loadable from 'react-loadable';
|
||||
import { ComponentClass } from 'react';
|
||||
import { ReactElement, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export type LoadableRendererProps = {
|
||||
onRenderFailure?: Function;
|
||||
onRenderSuccess?: Function;
|
||||
onRenderFailure?: (error: Error) => void;
|
||||
onRenderSuccess?: () => void;
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onRenderFailure() {},
|
||||
onRenderSuccess() {},
|
||||
type LoaderMap<Exports> = {
|
||||
[K in keyof Exports]: () => Promise<Exports[K]> | Exports[K];
|
||||
};
|
||||
|
||||
export interface LoadableRenderer<Props>
|
||||
extends
|
||||
ComponentClass<Props & LoadableRendererProps>,
|
||||
Loadable.LoadableComponent {}
|
||||
|
||||
export default function createLoadableRenderer<
|
||||
Props,
|
||||
Exports extends { [key: string]: any },
|
||||
>(options: Loadable.OptionsWithMap<Props, Exports>): LoadableRenderer<Props> {
|
||||
const LoadableRenderer = Loadable.Map<Props, Exports>(
|
||||
options,
|
||||
) as LoadableRenderer<Props>;
|
||||
|
||||
// Extends the behavior of LoadableComponent to provide post-render listeners
|
||||
class CustomLoadableRenderer extends LoadableRenderer {
|
||||
static defaultProps: object;
|
||||
|
||||
componentDidMount() {
|
||||
this.afterRender();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.afterRender();
|
||||
}
|
||||
|
||||
afterRender() {
|
||||
const { loaded, loading, error } = this.state;
|
||||
const { onRenderFailure, onRenderSuccess } = this.props;
|
||||
if (!loading) {
|
||||
if (error) {
|
||||
(onRenderFailure as Function)(error);
|
||||
} else if (loaded && Object.keys(loaded).length > 0) {
|
||||
(onRenderSuccess as Function)();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomLoadableRenderer.defaultProps = defaultProps;
|
||||
CustomLoadableRenderer.preload = LoadableRenderer.preload;
|
||||
|
||||
return CustomLoadableRenderer;
|
||||
export interface LoadingProps {
|
||||
error?: { toString(): string };
|
||||
}
|
||||
|
||||
export interface LoadableOptions<Props, Exports> {
|
||||
loader: LoaderMap<Exports>;
|
||||
loading: (loadingProps: LoadingProps) => ReactElement | null;
|
||||
render: (loaded: Exports, props: Props) => ReactElement;
|
||||
}
|
||||
|
||||
export interface LoadableRenderer<Props> {
|
||||
(props: Props & LoadableRendererProps): ReactElement | null;
|
||||
preload: () => Promise<unknown>;
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
export default function createLoadableRenderer<Props, Exports>(
|
||||
options: LoadableOptions<Props, Exports>,
|
||||
): LoadableRenderer<Props> {
|
||||
let promise: Promise<Exports> | null = null;
|
||||
let cachedResult: Exports | null = null;
|
||||
let cachedError: Error | null = null;
|
||||
|
||||
const load = (): Promise<Exports> => {
|
||||
if (promise) return promise;
|
||||
const keys = Object.keys(options.loader) as (keyof Exports)[];
|
||||
promise = Promise.all(
|
||||
keys.map(key => Promise.resolve(options.loader[key]())),
|
||||
).then(
|
||||
values => {
|
||||
const loaded = {} as Exports;
|
||||
keys.forEach((key, i) => {
|
||||
loaded[key] = values[i] as Exports[typeof key];
|
||||
});
|
||||
cachedResult = loaded;
|
||||
return loaded;
|
||||
},
|
||||
err => {
|
||||
cachedError = err instanceof Error ? err : new Error(String(err));
|
||||
throw cachedError;
|
||||
},
|
||||
);
|
||||
return promise;
|
||||
};
|
||||
|
||||
const Renderer: LoadableRenderer<Props> = props => {
|
||||
const [state, setState] = useState<{
|
||||
loaded: Exports | null;
|
||||
error: Error | null;
|
||||
}>(() => ({ loaded: cachedResult, error: cachedError }));
|
||||
|
||||
useEffect(() => {
|
||||
if (state.loaded || state.error) return undefined;
|
||||
let cancelled = false;
|
||||
load().then(
|
||||
loaded => {
|
||||
if (!cancelled) setState({ loaded, error: null });
|
||||
},
|
||||
err => {
|
||||
if (!cancelled) setState({ loaded: null, error: err });
|
||||
},
|
||||
);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [state.loaded, state.error]);
|
||||
|
||||
// Keep callback refs current without retriggering the post-load effect on
|
||||
// every prop update.
|
||||
const onRenderSuccessRef = useRef(props.onRenderSuccess);
|
||||
const onRenderFailureRef = useRef(props.onRenderFailure);
|
||||
onRenderSuccessRef.current = props.onRenderSuccess;
|
||||
onRenderFailureRef.current = props.onRenderFailure;
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
onRenderFailureRef.current?.(state.error);
|
||||
} else if (state.loaded && Object.keys(state.loaded).length > 0) {
|
||||
onRenderSuccessRef.current?.();
|
||||
}
|
||||
}, [state.loaded, state.error]);
|
||||
|
||||
if (state.error) {
|
||||
return options.loading({ error: state.error });
|
||||
}
|
||||
if (!state.loaded) {
|
||||
return options.loading({});
|
||||
}
|
||||
return options.render(state.loaded, props as Props);
|
||||
};
|
||||
|
||||
Renderer.preload = load;
|
||||
|
||||
return Renderer;
|
||||
}
|
||||
|
||||
@@ -386,11 +386,7 @@ function Rose(element: HTMLElement, props: RoseProps): void {
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'segment')
|
||||
.classed('clickable', true)
|
||||
.on('mouseover', mouseover)
|
||||
.on('mouseout', mouseout)
|
||||
.on('mousemove', mousemove)
|
||||
.on('click', click);
|
||||
.classed('clickable', true);
|
||||
|
||||
const labels = labelsWrap
|
||||
.selectAll('g')
|
||||
@@ -614,6 +610,11 @@ function Rose(element: HTMLElement, props: RoseProps): void {
|
||||
}
|
||||
}
|
||||
|
||||
ae.on('mouseover', mouseover)
|
||||
.on('mouseout', mouseout)
|
||||
.on('mousemove', mousemove)
|
||||
.on('click', click);
|
||||
|
||||
function updateActive(): void {
|
||||
const delay = d3.event.altKey ? 3000 : 300;
|
||||
legendWrap.datum(legendData(datum)).call(legend);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@math.gl/web-mercator": "^4.1.0",
|
||||
"mapbox-gl": "^3.23.0",
|
||||
"mapbox-gl": "^3.23.1",
|
||||
"maplibre-gl": "^5.24.0",
|
||||
"react-map-gl": "^8.1.0",
|
||||
"supercluster": "^8.0.1"
|
||||
|
||||
@@ -50,7 +50,6 @@ import Table, {
|
||||
import { RootState } from 'src/dashboard/types';
|
||||
import { usePermissions } from 'src/hooks/usePermissions';
|
||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
import { ensureAppRoot } from 'src/utils/pathUtils';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
import HeaderWithRadioGroup from '@superset-ui/core/components/Table/header-renderers/HeaderWithRadioGroup';
|
||||
import { useDatasetMetadataBar } from 'src/features/datasets/metadataBar/useDatasetMetadataBar';
|
||||
@@ -250,7 +249,7 @@ export default function DrillDetailPane({
|
||||
if (dashboardId) {
|
||||
payload.form_data = { dashboardId };
|
||||
}
|
||||
SupersetClient.postForm(ensureAppRoot('/api/v1/chart/data'), {
|
||||
SupersetClient.postForm('/api/v1/chart/data', {
|
||||
form_data: safeStringify(payload),
|
||||
}).catch(error => {
|
||||
addDangerToast(
|
||||
|
||||
@@ -48,7 +48,6 @@ import { Logger, LOG_ACTIONS_LOAD_CHART } from 'src/logger/LogUtils';
|
||||
import { allowCrossDomain as domainShardingEnabled } from 'src/utils/hostNamesConfig';
|
||||
import { updateDataMask } from 'src/dataMask/actions';
|
||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||
import { ensureAppRoot } from 'src/utils/pathUtils';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
import { extendedDayjs } from '@superset-ui/core/utils/dates';
|
||||
import type { Dispatch, Action, AnyAction } from 'redux';
|
||||
@@ -781,6 +780,15 @@ export function exploreJSON(
|
||||
handleChartDataResponse(response, json, useLegacyApi),
|
||||
)
|
||||
.then(queriesResponse => {
|
||||
// Drop stale responses: if a newer query has started for this chart,
|
||||
// its controller will have replaced ours in state, so ignore this
|
||||
// response to avoid clobbering newer data with older results.
|
||||
if (key != null) {
|
||||
const currentController = getState().charts?.[key]?.queryController;
|
||||
if (currentController && currentController !== controller) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
(queriesResponse as QueryData[]).forEach(
|
||||
(resultItem: QueryData & { applied_filters?: JsonObject[] }) =>
|
||||
dispatch(
|
||||
@@ -826,6 +834,15 @@ export function exploreJSON(
|
||||
);
|
||||
}
|
||||
|
||||
// Drop stale failures the same way we drop stale successes,
|
||||
// so a slow earlier request can't mark a newer one as failed.
|
||||
if (key != null) {
|
||||
const currentController = getState().charts?.[key]?.queryController;
|
||||
if (currentController && currentController !== controller) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (isFeatureEnabled(FeatureFlag.GlobalAsyncQueries)) {
|
||||
// In async mode we just pass the raw error response through
|
||||
return dispatch(
|
||||
@@ -934,7 +951,7 @@ export function redirectSQLLab(
|
||||
requestedQuery: payload,
|
||||
});
|
||||
} else {
|
||||
SupersetClient.postForm(ensureAppRoot(redirectUrl), {
|
||||
SupersetClient.postForm(redirectUrl, {
|
||||
form_data: safeStringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,6 +156,78 @@ describe('chart actions', () => {
|
||||
.mockImplementation((data: unknown) => Promise.resolve(data));
|
||||
});
|
||||
|
||||
test('should drop stale success dispatches when a newer controller has replaced ours in state', async () => {
|
||||
const chartKey = 'stale_success_test';
|
||||
const formData: Partial<QueryFormData> = {
|
||||
slice_id: 456,
|
||||
datasource: 'table__1',
|
||||
viz_type: 'table',
|
||||
};
|
||||
// A controller belonging to a *newer* in-flight request, already stored
|
||||
// in state by the time this thunk's response resolves.
|
||||
const newerController = new AbortController();
|
||||
const state: MockState = {
|
||||
charts: {
|
||||
[chartKey]: {
|
||||
queryController: newerController,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
conf: {
|
||||
SUPERSET_WEBSERVER_TIMEOUT: 60,
|
||||
},
|
||||
},
|
||||
};
|
||||
const getState = jest.fn(() => state);
|
||||
const dispatchMock = jest.fn();
|
||||
const getChartDataRequestSpy = jest
|
||||
.spyOn(actions, 'getChartDataRequest')
|
||||
.mockResolvedValue({
|
||||
response: { status: 200 } as Response,
|
||||
json: { result: [{ data: [{ stale: true }] }] },
|
||||
});
|
||||
const handleChartDataResponseSpy = jest
|
||||
.spyOn(actions, 'handleChartDataResponse')
|
||||
.mockResolvedValue([{ data: [{ stale: true }] }]);
|
||||
const updateDataMaskSpy = jest
|
||||
.spyOn(dataMaskActions, 'updateDataMask')
|
||||
.mockReturnValue({ type: 'UPDATE_DATA_MASK' } as ReturnType<
|
||||
typeof dataMaskActions.updateDataMask
|
||||
>);
|
||||
const getQuerySettingsStub = jest
|
||||
.spyOn(exploreUtils, 'getQuerySettings')
|
||||
.mockReturnValue([false, () => {}] as unknown as ReturnType<
|
||||
typeof exploreUtils.getQuerySettings
|
||||
>);
|
||||
|
||||
try {
|
||||
const thunkAction = actions.exploreJSON(
|
||||
formData as QueryFormData,
|
||||
false,
|
||||
undefined,
|
||||
chartKey,
|
||||
);
|
||||
await thunkAction(
|
||||
dispatchMock as unknown as actions.ChartThunkDispatch,
|
||||
getState as unknown as () => actions.RootState,
|
||||
undefined,
|
||||
);
|
||||
|
||||
// CHART_UPDATE_STARTED is fine (it ran before the gate),
|
||||
// but CHART_UPDATE_SUCCEEDED must NOT have fired with the stale data.
|
||||
const dispatchedTypes = dispatchMock.mock.calls.map(
|
||||
([action]) => action?.type,
|
||||
);
|
||||
expect(dispatchedTypes).toContain(actions.CHART_UPDATE_STARTED);
|
||||
expect(dispatchedTypes).not.toContain(actions.CHART_UPDATE_SUCCEEDED);
|
||||
} finally {
|
||||
getChartDataRequestSpy.mockRestore();
|
||||
handleChartDataResponseSpy.mockRestore();
|
||||
updateDataMaskSpy.mockRestore();
|
||||
getQuerySettingsStub.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test('should defer abort of previous controller to avoid Redux state mutation', async () => {
|
||||
jest.useFakeTimers();
|
||||
const chartKey = 'defer_abort_test';
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
screen,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { FeatureFlag } from '@superset-ui/core';
|
||||
import { supersetTheme } from '@apache-superset/core/theme';
|
||||
import {
|
||||
OPEN_FILTER_BAR_WIDTH,
|
||||
CLOSED_FILTER_BAR_WIDTH,
|
||||
@@ -489,6 +490,48 @@ test('should render ParentSize wrapper with height 100% for tabs', async () => {
|
||||
expect(tabPanels.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should apply min-height to the top-level tab drop target so tabs can be dropped on dashboards with content', () => {
|
||||
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
|
||||
100,
|
||||
jest.fn(),
|
||||
]);
|
||||
(fetchFaveStar as jest.Mock).mockReturnValue({ type: 'mock-action' });
|
||||
(setActiveTab as jest.Mock).mockReturnValue({ type: 'mock-action' });
|
||||
|
||||
const { getByTestId } = render(<DashboardBuilder />, {
|
||||
useRedux: true,
|
||||
store: storeWithState({
|
||||
...mockState,
|
||||
dashboardLayout: undoableDashboardLayout,
|
||||
dashboardState: { ...mockState.dashboardState, editMode: true },
|
||||
}),
|
||||
useDnd: true,
|
||||
useTheme: true,
|
||||
useRouter: true,
|
||||
});
|
||||
|
||||
const headerWrapper = getByTestId('dashboard-header-wrapper');
|
||||
|
||||
// The Droppable inside the header should have the empty-droptarget class
|
||||
// when there are no top-level tabs and edit mode is active. Without this
|
||||
// class (and its associated min-height CSS rule), the drop target has zero
|
||||
// height and users cannot drag tabs onto dashboards that already have
|
||||
// content.
|
||||
const droptarget = headerWrapper.querySelector('.empty-droptarget');
|
||||
expect(droptarget).toBeInTheDocument();
|
||||
|
||||
// Verify the StyledHeader CSS defines a non-zero min-height for
|
||||
// .empty-droptarget, derived from theme.sizeUnit * 4 to stay in sync
|
||||
// with the source rule in DashboardBuilder.tsx.
|
||||
expect(headerWrapper).toHaveStyleRule(
|
||||
'min-height',
|
||||
`${supersetTheme.sizeUnit * 4}px`,
|
||||
{
|
||||
target: '.empty-droptarget',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should maintain layout when switching between tabs', async () => {
|
||||
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
|
||||
100,
|
||||
|
||||
@@ -100,6 +100,10 @@ const StyledHeader = styled.div<{ filterBarWidth: number }>`
|
||||
z-index: 99;
|
||||
max-width: calc(100vw - ${filterBarWidth}px);
|
||||
|
||||
.empty-droptarget {
|
||||
min-height: ${theme.sizeUnit * 4}px;
|
||||
}
|
||||
|
||||
.empty-droptarget:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import 'src/public-path';
|
||||
|
||||
import { lazy, StrictMode, Suspense, useEffect } from 'react';
|
||||
import { lazy, Suspense, useEffect } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
import { BrowserRouter as Router, Route } from 'react-router-dom';
|
||||
import { Global } from '@emotion/react';
|
||||
@@ -197,11 +197,7 @@ function start() {
|
||||
if (!root) {
|
||||
root = createRoot(appMountPoint);
|
||||
}
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<EmbeddedApp />
|
||||
</StrictMode>,
|
||||
);
|
||||
root.render(<EmbeddedApp />);
|
||||
},
|
||||
err => {
|
||||
// something is most likely wrong with the guest token; reset the guard
|
||||
|
||||
@@ -66,7 +66,6 @@ export const ZoomConfigControl: FC<ZoomConfigsControlProps> = ({
|
||||
};
|
||||
|
||||
const onBaseWidthChange = (width: number) => {
|
||||
console.log('now in onbasewidthcahnge');
|
||||
setBaseWidth(width);
|
||||
if (!value) {
|
||||
return;
|
||||
|
||||
@@ -58,7 +58,9 @@ beforeEach(() => {
|
||||
});
|
||||
});
|
||||
|
||||
// Tests for exportChart URL prefix handling in streaming export
|
||||
// Tests for exportChart URL prefix handling in streaming export.
|
||||
// Streaming uses native fetch (not SupersetClient), so exportChart must apply
|
||||
// ensureAppRoot before passing the URL to onStartStreamingExport.
|
||||
test('exportChart v1 API passes prefixed URL to onStartStreamingExport when app root is configured', async () => {
|
||||
const appRoot = '/superset';
|
||||
ensureAppRoot.mockImplementation((path: string) => `${appRoot}${path}`);
|
||||
@@ -111,6 +113,24 @@ test('exportChart v1 API passes nested prefix for deeply nested deployments', as
|
||||
expect(callArgs.exportType).toBe('xlsx');
|
||||
});
|
||||
|
||||
// Regression test for the double-prefix bug: SupersetClient.postForm adds appRoot
|
||||
// internally via getUrl(), so the URL passed must NOT already be prefixed.
|
||||
test('exportChart v1 API calls postForm with unprefixed URL when app root is configured', async () => {
|
||||
const { SupersetClient } = jest.requireMock('@superset-ui/core');
|
||||
const appRoot = '/analytics';
|
||||
ensureAppRoot.mockImplementation((path: string) => `${appRoot}${path}`);
|
||||
|
||||
await exportChart({
|
||||
formData: baseFormData,
|
||||
resultFormat: 'csv',
|
||||
});
|
||||
|
||||
expect(SupersetClient.postForm).toHaveBeenCalledTimes(1);
|
||||
const [url] = SupersetClient.postForm.mock.calls[0];
|
||||
expect(url).toBe('/api/v1/chart/data');
|
||||
expect(url).not.toContain(appRoot);
|
||||
});
|
||||
|
||||
test('exportChart passes csv exportType for CSV exports', async () => {
|
||||
const onStartStreamingExport = jest.fn();
|
||||
|
||||
@@ -143,7 +163,7 @@ test('exportChart passes xlsx exportType for Excel exports', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('exportChart legacy API (useLegacyApi=true) passes prefixed URL with app root configured', async () => {
|
||||
test('exportChart legacy API (useLegacyApi=true) passes prefixed URL to onStartStreamingExport when app root is configured', async () => {
|
||||
const appRoot = '/superset';
|
||||
ensureAppRoot.mockImplementation((path: string) => `${appRoot}${path}`);
|
||||
|
||||
@@ -165,6 +185,8 @@ test('exportChart legacy API (useLegacyApi=true) passes prefixed URL with app ro
|
||||
|
||||
expect(onStartStreamingExport).toHaveBeenCalledTimes(1);
|
||||
const callArgs = onStartStreamingExport.mock.calls[0][0];
|
||||
// The legacy blueprint path is /superset/explore_json/; with appRoot=/superset the
|
||||
// full streaming URL is /superset/superset/explore_json/ (appRoot + blueprint prefix).
|
||||
expect(callArgs.url).toBe('/superset/superset/explore_json/?csv=true');
|
||||
expect(callArgs.exportType).toBe('csv');
|
||||
});
|
||||
|
||||
@@ -76,6 +76,7 @@ interface GetExploreUrlParams {
|
||||
allowDomainSharding?: boolean;
|
||||
method?: 'GET' | 'POST';
|
||||
relative?: boolean;
|
||||
includeAppRoot?: boolean;
|
||||
}
|
||||
|
||||
interface BuildV1ChartDataPayloadParams {
|
||||
@@ -223,6 +224,7 @@ export function getExploreUrl({
|
||||
allowDomainSharding = false,
|
||||
method = 'POST',
|
||||
relative = false,
|
||||
includeAppRoot = true,
|
||||
}: GetExploreUrlParams): string | null {
|
||||
if (!formData.datasource) {
|
||||
return null;
|
||||
@@ -242,7 +244,7 @@ export function getExploreUrl({
|
||||
uri = URI(URI(curUrl).search());
|
||||
}
|
||||
|
||||
const directory = getURIDirectory(endpointType);
|
||||
const directory = getURIDirectory(endpointType, includeAppRoot);
|
||||
|
||||
// Building the querystring (search) part of the URI
|
||||
const search = uri.search(true) as Record<string, string>;
|
||||
@@ -370,10 +372,11 @@ export const exportChart = async ({
|
||||
force,
|
||||
allowDomainSharding: false,
|
||||
relative: true,
|
||||
includeAppRoot: false,
|
||||
});
|
||||
payload = formData;
|
||||
} else {
|
||||
url = ensureAppRoot('/api/v1/chart/data');
|
||||
url = '/api/v1/chart/data';
|
||||
payload = await buildV1ChartDataPayload({
|
||||
formData,
|
||||
force,
|
||||
@@ -385,14 +388,16 @@ export const exportChart = async ({
|
||||
|
||||
// Check if streaming export handler is provided (from dashboard Chart.jsx)
|
||||
if (onStartStreamingExport) {
|
||||
// Streaming is handled by the caller - pass URL, payload, and export type
|
||||
// Streaming uses native fetch — apply appRoot prefix here since useStreamingExport
|
||||
// does not go through SupersetClient (which would add it automatically).
|
||||
onStartStreamingExport({
|
||||
url,
|
||||
url: url ? ensureAppRoot(url) : url,
|
||||
payload,
|
||||
exportType: resultFormat,
|
||||
});
|
||||
} else {
|
||||
// Fallback to original behavior for non-streaming exports
|
||||
// SupersetClient.postForm calls getUrl({ endpoint }) internally, which prepends
|
||||
// appRoot — so the URL must NOT be pre-prefixed here.
|
||||
SupersetClient.postForm(url as string, {
|
||||
form_data: safeStringify(payload),
|
||||
});
|
||||
|
||||
@@ -255,22 +255,107 @@ const EnumNamesRenderer = withJsonFormsControlProps(EnumNamesControl);
|
||||
const enumNamesEntry = {
|
||||
// Rank 5: higher than the default string renderer (2–3) so this fires
|
||||
// whenever x-enumNames is present, regardless of the underlying type.
|
||||
// Array-of-enum schemas are handled by ``multiEnumEntry`` below — this
|
||||
// renderer only targets scalar string/number controls.
|
||||
tester: rankWith(
|
||||
5,
|
||||
schemaMatches(s => {
|
||||
const names = (s as Record<string, unknown>)['x-enumNames'];
|
||||
return Array.isArray(names) && (names as unknown[]).length > 0;
|
||||
}),
|
||||
and(
|
||||
schemaMatches(s => {
|
||||
const names = (s as Record<string, unknown>)['x-enumNames'];
|
||||
return Array.isArray(names) && (names as unknown[]).length > 0;
|
||||
}),
|
||||
schemaMatches(
|
||||
s => (s as Record<string, unknown>)?.type !== 'array',
|
||||
),
|
||||
),
|
||||
),
|
||||
renderer: EnumNamesRenderer,
|
||||
};
|
||||
|
||||
/**
|
||||
* Renderer for ``{type: 'array', items: {enum: [...]}}`` schemas. Renders
|
||||
* a single Antd Select with ``mode="multiple"`` (tag-style multi-select),
|
||||
* matching the natural expectation of a "pick several from a list" control.
|
||||
*
|
||||
* Without this, the default ``PrimitiveArrayControl`` from the upstream
|
||||
* library renders an "Add …" button that creates one single-select per
|
||||
* element — visually wrong for an enum multi-select and unable to display
|
||||
* ``items.x-enumNames`` labels.
|
||||
*
|
||||
* The renderer is dynamic-aware: when the host form is refreshing the
|
||||
* schema (e.g. compatible options narrowing as the user picks), the Select
|
||||
* shows a loading indicator without becoming disabled, so the user can
|
||||
* continue editing while options refresh.
|
||||
*/
|
||||
function MultiEnumControl(props: ControlProps) {
|
||||
const { refreshingSchema } = props.config ?? {};
|
||||
const arraySchema = props.schema as Record<string, unknown>;
|
||||
const itemsSchema =
|
||||
(arraySchema.items as Record<string, unknown>) ??
|
||||
({} as Record<string, unknown>);
|
||||
|
||||
const enumValues = (itemsSchema.enum as unknown[]) ?? [];
|
||||
const enumNames =
|
||||
(itemsSchema['x-enumNames'] as string[]) ?? enumValues.map(String);
|
||||
|
||||
const options = enumValues.map((value, index) => ({
|
||||
value: value as string | number,
|
||||
label: enumNames[index] ?? String(value),
|
||||
}));
|
||||
|
||||
const value = Array.isArray(props.data) ? (props.data as unknown[]) : [];
|
||||
|
||||
const tooltip = (props.uischema?.options as Record<string, unknown>)
|
||||
?.tooltip as string | undefined;
|
||||
|
||||
return (
|
||||
<Form.Item label={props.label} tooltip={tooltip}>
|
||||
<Select
|
||||
mode="multiple"
|
||||
value={value as (string | number)[]}
|
||||
onChange={next => props.handleChange(props.path, next)}
|
||||
options={options}
|
||||
style={{ width: '100%' }}
|
||||
disabled={!props.enabled}
|
||||
loading={!!refreshingSchema}
|
||||
allowClear
|
||||
optionFilterProp="label"
|
||||
placeholder={
|
||||
(props.uischema?.options as Record<string, unknown>)
|
||||
?.placeholderText as string | undefined
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
const MultiEnumRenderer = withJsonFormsControlProps(MultiEnumControl);
|
||||
const multiEnumEntry = {
|
||||
// Rank 35: must beat upstream ``PrimitiveArrayRenderer`` (rank 30) so an
|
||||
// ``array``/``items.enum`` schema renders as one Antd multi-select tag
|
||||
// box instead of the "Add" repeater pattern that PrimitiveArray uses.
|
||||
tester: rankWith(
|
||||
35,
|
||||
schemaMatches(s => {
|
||||
const schema = s as Record<string, unknown>;
|
||||
if (schema?.type !== 'array') return false;
|
||||
const items = schema.items as Record<string, unknown> | undefined;
|
||||
return (
|
||||
!!items &&
|
||||
Array.isArray(items.enum) &&
|
||||
(items.enum as unknown[]).length > 0
|
||||
);
|
||||
}),
|
||||
),
|
||||
renderer: MultiEnumRenderer,
|
||||
};
|
||||
|
||||
export const renderers = [
|
||||
...rendererRegistryEntries,
|
||||
passwordEntry,
|
||||
constEntry,
|
||||
readOnlyEntry,
|
||||
enumNamesEntry,
|
||||
multiEnumEntry,
|
||||
dynamicFieldEntry,
|
||||
];
|
||||
|
||||
|
||||
@@ -254,7 +254,9 @@ export default function AddSemanticViewModal({
|
||||
!schema?.properties ||
|
||||
Object.keys(schema.properties).length === 0
|
||||
) {
|
||||
// No runtime config needed — fetch views right away
|
||||
// Preserve top-level runtime metadata (e.g. x-singleView) even when
|
||||
// there are no form fields, then fetch views right away.
|
||||
applyRuntimeSchema(schema);
|
||||
fetchViews(uuid, {}, gen);
|
||||
} else {
|
||||
applyRuntimeSchema(schema);
|
||||
@@ -456,6 +458,32 @@ export default function AddSemanticViewModal({
|
||||
const viewsDisabled =
|
||||
loadingViews || (!loadingViews && availableViews.length === 0);
|
||||
|
||||
// When ``x-singleView: true`` the runtime form fully describes a single
|
||||
// semantic view (e.g. a MetricFlow cube). Hide the picker and auto-select
|
||||
// whatever ``get_semantic_views`` returned so the Add button can fire
|
||||
// without an extra user click.
|
||||
const singleViewMode =
|
||||
(runtimeSchema as Record<string, unknown> | null)?.['x-singleView'] ===
|
||||
true;
|
||||
|
||||
useEffect(() => {
|
||||
if (!singleViewMode) return;
|
||||
const namesToAdd = availableViews
|
||||
.filter(v => !v.already_added)
|
||||
.map(v => v.name)
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.slice(0, 1);
|
||||
setSelectedViewNames(prev => {
|
||||
if (
|
||||
prev.length === namesToAdd.length &&
|
||||
prev.every((n, i) => n === namesToAdd[i])
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
return namesToAdd;
|
||||
});
|
||||
}, [singleViewMode, availableViews]);
|
||||
|
||||
return (
|
||||
<StandardModal
|
||||
show={show}
|
||||
@@ -511,8 +539,12 @@ export default function AddSemanticViewModal({
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Semantic Views — always visible once a layer is selected */}
|
||||
{selectedLayerUuid && !loadingRuntime && (
|
||||
{/* Semantic Views — always visible once a layer is selected, unless
|
||||
the runtime schema declares ``x-singleView: true``: extensions
|
||||
(e.g. MetricFlow cubes) whose runtime form fully describes a
|
||||
single view set that flag so the picker disappears and the
|
||||
view is auto-selected when ``get_semantic_views`` returns it. */}
|
||||
{selectedLayerUuid && !loadingRuntime && !singleViewMode && (
|
||||
<ModalFormField label={t('Semantic Views')}>
|
||||
<Select
|
||||
ariaLabel={t('Semantic views')}
|
||||
|
||||
@@ -17,33 +17,4 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
.versionBanner {
|
||||
background-color: var(--ifm-color-emphasis-100);
|
||||
padding: 0.5rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--ifm-color-emphasis-200);
|
||||
}
|
||||
|
||||
.versionContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: var(--ifm-container-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--ifm-spacing-horizontal);
|
||||
}
|
||||
|
||||
.versionLabel {
|
||||
font-weight: bold;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.versionSelector {
|
||||
cursor: pointer;
|
||||
color: var(--ifm-color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.versionSelector:hover {
|
||||
text-decoration: none;
|
||||
color: var(--ifm-color-primary-darker);
|
||||
}
|
||||
/// <reference types="@emotion/jest" />
|
||||
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
import 'src/public-path';
|
||||
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { logging } from '@apache-superset/core/utils';
|
||||
import initPreamble from 'src/preamble';
|
||||
@@ -32,11 +31,7 @@ if (appMountPoint) {
|
||||
await initPreamble();
|
||||
} finally {
|
||||
const { default: App } = await import(/* webpackMode: "eager" */ './App');
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
root.render(<App />);
|
||||
}
|
||||
})().catch(err => {
|
||||
logging.error('Unhandled error during app initialization', err);
|
||||
|
||||
@@ -20,7 +20,6 @@ import 'src/public-path';
|
||||
|
||||
// Menu App. Used in views that do not already include the Menu component in the layout.
|
||||
// eg, backend rendered views
|
||||
import { StrictMode } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
@@ -46,26 +45,24 @@ const emotionCache = createCache({
|
||||
});
|
||||
|
||||
const app = (
|
||||
<StrictMode>
|
||||
<CacheProvider value={emotionCache}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<QueryParamProvider
|
||||
adapter={ReactRouter5Adapter}
|
||||
options={{
|
||||
searchStringToObject: querystring.parse,
|
||||
objectToSearchString: (object: Record<string, any>) =>
|
||||
querystring.stringify(object, { encode: false }),
|
||||
}}
|
||||
>
|
||||
<Menu data={menu} />
|
||||
</QueryParamProvider>
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
</StrictMode>
|
||||
<CacheProvider value={emotionCache}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<QueryParamProvider
|
||||
adapter={ReactRouter5Adapter}
|
||||
options={{
|
||||
searchStringToObject: querystring.parse,
|
||||
objectToSearchString: (object: Record<string, any>) =>
|
||||
querystring.stringify(object, { encode: false }),
|
||||
}}
|
||||
>
|
||||
<Menu data={menu} />
|
||||
</QueryParamProvider>
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
);
|
||||
|
||||
const menuMountPoint = document.getElementById('app-menu');
|
||||
|
||||
@@ -1212,6 +1212,17 @@ def _resolve_viz_type(config: Any) -> str:
|
||||
return "unknown"
|
||||
|
||||
|
||||
TABLE_VIZ_TYPE_LABELS = {
|
||||
"table": "table chart",
|
||||
"ag-grid-table": "interactive table chart",
|
||||
}
|
||||
|
||||
|
||||
def get_table_chart_type_label(viz_type: str | None) -> str | None:
|
||||
"""Return a user-facing label for table-family Superset viz types."""
|
||||
return TABLE_VIZ_TYPE_LABELS.get(viz_type) if viz_type is not None else None
|
||||
|
||||
|
||||
def analyze_chart_capabilities(chart: Any | None, config: Any) -> ChartCapabilities:
|
||||
"""Analyze chart capabilities based on type and configuration."""
|
||||
if chart:
|
||||
|
||||
@@ -1872,6 +1872,12 @@ class GenerateChartResponse(BaseModel):
|
||||
|
||||
# Navigation and context
|
||||
explore_url: str | None = Field(None, description="Edit chart in Superset")
|
||||
chart_type_label: str | None = Field(
|
||||
None,
|
||||
description=(
|
||||
"User-facing chart type label derived from the rendered visualization type"
|
||||
),
|
||||
)
|
||||
embed_code: str | None = Field(None, description="HTML embed snippet")
|
||||
api_endpoints: Dict[str, str] = Field(
|
||||
default_factory=dict, description="Related API endpoints for data/updates"
|
||||
|
||||
@@ -35,6 +35,7 @@ from superset.mcp_service.chart.chart_utils import (
|
||||
analyze_chart_capabilities,
|
||||
analyze_chart_semantics,
|
||||
generate_chart_name,
|
||||
get_table_chart_type_label,
|
||||
map_config_to_form_data,
|
||||
validate_chart_dataset,
|
||||
)
|
||||
@@ -746,6 +747,7 @@ async def generate_chart( # noqa: C901
|
||||
"capabilities": capabilities.model_dump() if capabilities else None,
|
||||
"semantics": semantics.model_dump() if semantics else None,
|
||||
"explore_url": explore_url,
|
||||
"chart_type_label": get_table_chart_type_label(form_data.get("viz_type")),
|
||||
# Form data fields - REQUIRED for chatbot/external client rendering
|
||||
"form_data": _sanitize_generate_chart_form_data_for_llm_context(form_data),
|
||||
"form_data_key": form_data_key,
|
||||
|
||||
@@ -33,6 +33,7 @@ from superset.mcp_service.auth import has_dataset_access
|
||||
from superset.mcp_service.chart.chart_helpers import extract_form_data_key_from_url
|
||||
from superset.mcp_service.chart.chart_utils import (
|
||||
generate_explore_link as generate_url,
|
||||
get_table_chart_type_label,
|
||||
map_config_to_form_data,
|
||||
)
|
||||
from superset.mcp_service.chart.compile import validate_and_compile
|
||||
@@ -130,6 +131,7 @@ async def generate_explore_link(
|
||||
"url": "",
|
||||
"form_data": {},
|
||||
"form_data_key": None,
|
||||
"chart_type_label": None,
|
||||
"error": (
|
||||
f"Dataset not found: {request.dataset_id}. "
|
||||
"Use list_datasets to find valid dataset IDs."
|
||||
@@ -148,6 +150,7 @@ async def generate_explore_link(
|
||||
"url": "",
|
||||
"form_data": {},
|
||||
"form_data_key": None,
|
||||
"chart_type_label": None,
|
||||
"error": (
|
||||
f"Dataset not found: {request.dataset_id}. "
|
||||
"Use list_datasets to find valid dataset IDs."
|
||||
@@ -217,6 +220,7 @@ async def generate_explore_link(
|
||||
"url": "",
|
||||
"form_data": form_data,
|
||||
"form_data_key": None,
|
||||
"chart_type_label": None,
|
||||
"error": error_payload,
|
||||
}
|
||||
|
||||
@@ -243,6 +247,7 @@ async def generate_explore_link(
|
||||
"url": explore_url,
|
||||
"form_data": form_data,
|
||||
"form_data_key": form_data_key,
|
||||
"chart_type_label": get_table_chart_type_label(form_data.get("viz_type")),
|
||||
"error": None,
|
||||
}
|
||||
|
||||
@@ -260,5 +265,6 @@ async def generate_explore_link(
|
||||
"url": "",
|
||||
"form_data": {},
|
||||
"form_data_key": None,
|
||||
"chart_type_label": None,
|
||||
"error": f"Failed to generate explore link: {str(e)}",
|
||||
}
|
||||
|
||||
@@ -27,6 +27,55 @@ from superset.utils.pandas_postprocessing.utils import (
|
||||
)
|
||||
|
||||
|
||||
def _restore_dropped_metric_columns(
|
||||
df: DataFrame,
|
||||
expected_metrics: list[str],
|
||||
orig_columns: Optional[DataFrame],
|
||||
) -> DataFrame:
|
||||
"""Re-add metric columns that pivot_table dropped due to all-NaN values.
|
||||
|
||||
When drop_missing_columns=True, pandas pivot_table silently removes columns
|
||||
whose entries are all NaN. This breaks downstream post-processing steps
|
||||
(rename, rolling) that use validate_column_args to assert the columns exist.
|
||||
Restoring the columns as all-NaN preserves the expected schema while still
|
||||
allowing sparse category combinations to be dropped — only metric-level
|
||||
absences are restored.
|
||||
|
||||
Note: this intentionally changes the visible output of drop_missing_columns=True
|
||||
for all-NaN metrics: they are kept as empty series rather than dropped. This is
|
||||
necessary for chart-rendering post-processing to maintain schema stability.
|
||||
|
||||
:param df: Post-pivot DataFrame (may have MultiIndex or flat columns).
|
||||
:param expected_metrics: Metric column names that should exist at level 0.
|
||||
:param orig_columns: Pre-pivot slice of the groupby column(s), used to
|
||||
lazily compute (metric, *col_vals) restoration keys for only the
|
||||
metrics that were entirely absent after pivoting. None for flat pivots.
|
||||
"""
|
||||
if orig_columns is not None:
|
||||
# MultiIndex case. Only compute keys for metrics that were entirely
|
||||
# dropped — skips metrics still present, avoiding O(n_rows × n_metrics)
|
||||
# upfront work when no all-NaN drop occurred.
|
||||
existing_metrics = (
|
||||
set(df.columns.get_level_values(0)) if len(df.columns) > 0 else set()
|
||||
)
|
||||
missing = {m for m in expected_metrics if m not in existing_metrics}
|
||||
if missing:
|
||||
# Dict preserves data-insertion order and deduplicates, so restored
|
||||
# columns appear in deterministic order.
|
||||
keys_dict: dict[tuple[Any, ...], None] = {}
|
||||
for row in orig_columns.itertuples():
|
||||
for metric in missing:
|
||||
keys_dict[(metric, *row[1:])] = None
|
||||
for key in keys_dict:
|
||||
df[key] = float("nan")
|
||||
else:
|
||||
# Flat case (no groupby columns): restore simple metric columns.
|
||||
for metric in expected_metrics:
|
||||
if metric not in df.columns:
|
||||
df[metric] = float("nan")
|
||||
return df
|
||||
|
||||
|
||||
@validate_column_args("index", "columns")
|
||||
def pivot( # pylint: disable=too-many-arguments
|
||||
df: DataFrame,
|
||||
@@ -50,7 +99,11 @@ def pivot( # pylint: disable=too-many-arguments
|
||||
:param column_fill_value: Value to replace missing pivot columns with. By default
|
||||
replaces missing values with "<NULL>". Set to `None` to remove columns
|
||||
with missing values.
|
||||
:param drop_missing_columns: Do not include columns whose entries are all missing
|
||||
:param drop_missing_columns: Do not include columns whose entries are all missing.
|
||||
Note: metric columns entirely absent after pivoting (the whole metric is
|
||||
all-NaN) are restored as empty series so that downstream post-processing
|
||||
(rename, rolling) can reference them. Sparse category combinations where
|
||||
only some (metric, category) pairs are all-NaN may still be dropped.
|
||||
:param combine_value_with_metric: Display metrics side by side within each column,
|
||||
as opposed to each column being displayed side by side for each metric.
|
||||
:param aggregates: A mapping from aggregate column name to the aggregate
|
||||
@@ -79,15 +132,20 @@ def pivot( # pylint: disable=too-many-arguments
|
||||
# Remove once/if support is added.
|
||||
aggfunc = {na.column: na.aggfunc for na in aggregate_funcs.values()}
|
||||
|
||||
# When dropna = False, the pivot_table function will calculate cartesian-product
|
||||
# for MultiIndex.
|
||||
# For drop_missing_columns=False: pre-compute all (metric, *col_vals) tuples
|
||||
# to filter Cartesian-product columns after pivoting.
|
||||
# For drop_missing_columns=True: save a slice of the groupby column data so
|
||||
# that _restore_dropped_metric_columns can build keys lazily — only for metrics
|
||||
# that were actually dropped, avoiding O(n_rows × n_metrics) upfront work in
|
||||
# the common case where no metric is entirely all-NaN.
|
||||
# https://github.com/apache/superset/issues/15956
|
||||
# https://github.com/pandas-dev/pandas/issues/18030
|
||||
series_set = set()
|
||||
pivot_key_set: set[tuple[Any, ...]] = set()
|
||||
if not drop_missing_columns and columns:
|
||||
for row in df[columns].itertuples():
|
||||
for metric in aggfunc.keys():
|
||||
series_set.add(tuple([metric]) + tuple(row[1:])) # noqa: C409
|
||||
pivot_key_set.add((metric, *row[1:]))
|
||||
orig_columns_df = df[columns] if columns else None
|
||||
|
||||
df = df.pivot_table(
|
||||
values=aggfunc.keys(),
|
||||
@@ -100,10 +158,14 @@ def pivot( # pylint: disable=too-many-arguments
|
||||
margins_name=marginal_distribution_name,
|
||||
)
|
||||
|
||||
if not drop_missing_columns and len(series_set) > 0 and not df.empty:
|
||||
df = df.drop(df.columns.difference(series_set), axis=PandasAxis.COLUMN)
|
||||
if drop_missing_columns:
|
||||
df = _restore_dropped_metric_columns(df, list(aggfunc.keys()), orig_columns_df)
|
||||
elif pivot_key_set and not df.empty:
|
||||
df = df.drop(df.columns.difference(pivot_key_set), axis=PandasAxis.COLUMN)
|
||||
|
||||
if combine_value_with_metric:
|
||||
df = df.stack(0).unstack()
|
||||
# dropna=False preserves restored all-NaN metric rows that would otherwise
|
||||
# be silently dropped by stack's default dropna=True behavior.
|
||||
df = df.stack(level=0, dropna=False).unstack()
|
||||
|
||||
return df
|
||||
|
||||
@@ -25,11 +25,27 @@ from pydantic import ValidationError
|
||||
from superset.mcp_service.chart.schemas import (
|
||||
ColumnRef,
|
||||
GenerateChartRequest,
|
||||
GenerateChartResponse,
|
||||
TableChartConfig,
|
||||
XYChartConfig,
|
||||
)
|
||||
|
||||
|
||||
class TestGenerateChartResponse:
|
||||
"""Test GenerateChartResponse validation."""
|
||||
|
||||
def test_chart_type_label_accepted(self) -> None:
|
||||
response = GenerateChartResponse.model_validate(
|
||||
{
|
||||
"success": True,
|
||||
"chart_type_label": "table chart",
|
||||
"form_data": {"viz_type": "table"},
|
||||
}
|
||||
)
|
||||
|
||||
assert response.chart_type_label == "table chart"
|
||||
|
||||
|
||||
class TestTableChartConfig:
|
||||
"""Test TableChartConfig validation."""
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from superset.mcp_service.chart.chart_utils import (
|
||||
create_metric_object,
|
||||
generate_chart_name,
|
||||
generate_explore_link,
|
||||
get_table_chart_type_label,
|
||||
is_column_truly_temporal,
|
||||
map_config_to_form_data,
|
||||
map_filter_operator,
|
||||
@@ -49,6 +50,27 @@ from superset.mcp_service.chart.schemas import (
|
||||
from superset.utils.core import FilterOperator, GenericDataType
|
||||
|
||||
|
||||
class TestGetTableChartTypeLabel:
|
||||
"""Test user-facing labels for table-family chart types."""
|
||||
|
||||
def test_regular_table_label(self) -> None:
|
||||
assert get_table_chart_type_label("table") == "table chart"
|
||||
|
||||
def test_ag_grid_table_label(self) -> None:
|
||||
assert get_table_chart_type_label("ag-grid-table") == (
|
||||
"interactive table chart"
|
||||
)
|
||||
|
||||
def test_non_table_viz_type_has_no_label(self) -> None:
|
||||
assert get_table_chart_type_label("echarts_timeseries_bar") is None
|
||||
|
||||
def test_unknown_viz_type_has_no_label(self) -> None:
|
||||
assert get_table_chart_type_label("my-custom-chart") is None
|
||||
|
||||
def test_missing_viz_type_has_no_label(self) -> None:
|
||||
assert get_table_chart_type_label(None) is None
|
||||
|
||||
|
||||
class TestCreateMetricObject:
|
||||
"""Test create_metric_object function"""
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
Unit tests for MCP generate_chart tool
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.orm.exc import DetachedInstanceError
|
||||
@@ -37,6 +37,7 @@ from superset.mcp_service.chart.tool.generate_chart import (
|
||||
_compile_chart,
|
||||
_sanitize_generate_chart_form_data_for_llm_context,
|
||||
CompileResult,
|
||||
generate_chart,
|
||||
)
|
||||
from superset.mcp_service.utils import sanitize_for_llm_context
|
||||
from superset.utils import json as utils_json
|
||||
@@ -45,6 +46,58 @@ from superset.utils import json as utils_json
|
||||
class TestGenerateChart:
|
||||
"""Tests for generate_chart MCP tool."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_chart_returns_table_chart_type_label(self) -> None:
|
||||
"""Test chart generation response includes table chart type label."""
|
||||
request = GenerateChartRequest(
|
||||
dataset_id="1",
|
||||
config=TableChartConfig(
|
||||
chart_type="table",
|
||||
columns=[ColumnRef(name="region")],
|
||||
),
|
||||
preview_formats=["url"],
|
||||
)
|
||||
ctx = MagicMock()
|
||||
ctx.info = AsyncMock()
|
||||
ctx.debug = AsyncMock()
|
||||
ctx.warning = AsyncMock()
|
||||
ctx.error = AsyncMock()
|
||||
ctx.report_progress = AsyncMock()
|
||||
validation_result = Mock(
|
||||
is_valid=True,
|
||||
request=request,
|
||||
warnings={},
|
||||
error=None,
|
||||
)
|
||||
mock_user = Mock()
|
||||
mock_user.id = 1
|
||||
mock_user.username = "admin"
|
||||
mock_user.roles = []
|
||||
mock_user.groups = []
|
||||
|
||||
with (
|
||||
patch(
|
||||
"superset.mcp_service.auth.get_user_from_request",
|
||||
return_value=mock_user,
|
||||
),
|
||||
patch(
|
||||
"superset.mcp_service.chart.validation.ValidationPipeline."
|
||||
"validate_request_with_warnings",
|
||||
return_value=validation_result,
|
||||
),
|
||||
patch(
|
||||
"superset.mcp_service.chart.chart_utils.generate_explore_link",
|
||||
return_value=(
|
||||
"http://localhost:9001/explore/?"
|
||||
"form_data_key=test_form_data_key_123"
|
||||
),
|
||||
),
|
||||
patch("superset.daos.dataset.DatasetDAO.find_by_id", return_value=None),
|
||||
):
|
||||
result = await generate_chart(request, ctx=ctx)
|
||||
|
||||
assert result.chart_type_label == "table chart"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_chart_request_structure(self):
|
||||
"""Test that chart generation request structures are properly formed."""
|
||||
|
||||
@@ -158,6 +158,7 @@ class TestGenerateExploreLink:
|
||||
result.data["url"]
|
||||
== "http://localhost:9001/explore/?form_data_key=test_form_data_key_123"
|
||||
)
|
||||
assert result.data["chart_type_label"] == "table chart"
|
||||
mock_create_form_data.assert_called_once()
|
||||
|
||||
@patch("superset.daos.dataset.DatasetDAO.find_by_id")
|
||||
@@ -197,8 +198,36 @@ class TestGenerateExploreLink:
|
||||
result.data["url"]
|
||||
== "http://localhost:9001/explore/?form_data_key=comprehensive_key_456"
|
||||
)
|
||||
assert result.data["chart_type_label"] == "table chart"
|
||||
mock_create_form_data.assert_called_once()
|
||||
|
||||
@patch("superset.daos.dataset.DatasetDAO.find_by_id")
|
||||
@patch(
|
||||
"superset.mcp_service.commands.create_form_data.MCPCreateFormDataCommand.run"
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_ag_grid_table_explore_link_label(
|
||||
self, mock_create_form_data, mock_find_dataset, mcp_server
|
||||
) -> None:
|
||||
"""Test generating explore link reports AG Grid table label."""
|
||||
mock_create_form_data.return_value = "ag_grid_key_123"
|
||||
mock_find_dataset.return_value = _mock_dataset(id=1)
|
||||
|
||||
config = TableChartConfig(
|
||||
chart_type="table",
|
||||
viz_type="ag-grid-table",
|
||||
columns=[ColumnRef(name="region")],
|
||||
)
|
||||
request = GenerateExploreLinkRequest(dataset_id="1", config=config)
|
||||
|
||||
async with Client(mcp_server) as client:
|
||||
result = await client.call_tool(
|
||||
"generate_explore_link", {"request": request.model_dump()}
|
||||
)
|
||||
|
||||
assert result.data["error"] is None
|
||||
assert result.data["chart_type_label"] == "interactive table chart"
|
||||
|
||||
@patch("superset.daos.dataset.DatasetDAO.find_by_id")
|
||||
@patch(
|
||||
"superset.mcp_service.commands.create_form_data.MCPCreateFormDataCommand.run"
|
||||
@@ -236,6 +265,7 @@ class TestGenerateExploreLink:
|
||||
result.data["url"]
|
||||
== "http://localhost:9001/explore/?form_data_key=line_chart_key_789"
|
||||
)
|
||||
assert result.data["chart_type_label"] is None
|
||||
mock_create_form_data.assert_called_once()
|
||||
|
||||
@patch("superset.daos.dataset.DatasetDAO.find_by_id")
|
||||
@@ -690,6 +720,7 @@ class TestGenerateExploreLink:
|
||||
assert result.data["url"] == ""
|
||||
assert result.data["form_data"] == {}
|
||||
assert result.data["form_data_key"] is None
|
||||
assert result.data["chart_type_label"] is None
|
||||
assert "Invalid config structure" in result.data["error"]
|
||||
finally:
|
||||
# Restore original function
|
||||
@@ -775,6 +806,7 @@ class TestGenerateExploreLink:
|
||||
assert result.data["url"] == ""
|
||||
assert result.data["form_data"] == {}
|
||||
assert result.data["form_data_key"] is None
|
||||
assert result.data["chart_type_label"] is None
|
||||
assert "Dataset not found: 99999" in result.data["error"]
|
||||
assert "list_datasets" in result.data["error"]
|
||||
|
||||
@@ -801,6 +833,7 @@ class TestGenerateExploreLink:
|
||||
assert result.data["url"] == ""
|
||||
assert result.data["form_data"] == {}
|
||||
assert result.data["form_data_key"] is None
|
||||
assert result.data["chart_type_label"] is None
|
||||
assert "Dataset not found" in result.data["error"]
|
||||
|
||||
|
||||
@@ -1015,6 +1048,7 @@ class TestGenerateExploreLinkValidation:
|
||||
|
||||
assert result.data["url"] == ""
|
||||
assert result.data["form_data_key"] is None
|
||||
assert result.data["chart_type_label"] is None
|
||||
error = result.data["error"]
|
||||
assert isinstance(error, dict)
|
||||
assert error["error_code"] == "CHART_VALIDATION_FAILED"
|
||||
@@ -1050,6 +1084,7 @@ class TestGenerateExploreLinkValidation:
|
||||
)
|
||||
|
||||
assert result.data["url"] == ""
|
||||
assert result.data["chart_type_label"] is None
|
||||
# Surface as "not found" rather than leaking that the dataset exists.
|
||||
assert "Dataset not found" in result.data["error"]
|
||||
mock_create_form_data.assert_not_called()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# under the License.
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from pandas import DataFrame, to_datetime
|
||||
|
||||
@@ -203,3 +204,245 @@ def test_pivot_eliminate_cartesian_product_columns():
|
||||
"metric2, 1, 1",
|
||||
]
|
||||
assert np.isnan(df["metric, 1, 1"][0])
|
||||
|
||||
|
||||
def test_pivot_preserves_all_nan_metric_flat():
|
||||
"""
|
||||
Pivot with drop_missing_columns=True must not drop metric columns whose entries
|
||||
are all NaN. This prevents downstream post-processing (e.g. rename) from failing
|
||||
with "Referenced columns not available in DataFrame" when a Jinja metric
|
||||
expression evaluates to NULL for every row (SC-100398).
|
||||
"""
|
||||
mock_df = DataFrame(
|
||||
{
|
||||
"dttm": to_datetime(["2019-01-01", "2019-01-02", "2019-01-03"]),
|
||||
"metric": [np.nan, np.nan, np.nan],
|
||||
}
|
||||
)
|
||||
|
||||
df = pivot(
|
||||
df=mock_df,
|
||||
index=["dttm"],
|
||||
aggregates={"metric": {"operator": "mean"}},
|
||||
drop_missing_columns=True,
|
||||
)
|
||||
|
||||
assert "metric" in df.columns
|
||||
assert df["metric"].isna().all()
|
||||
|
||||
|
||||
def test_pivot_preserves_all_nan_metric_with_columns():
|
||||
"""
|
||||
Pivot with groupby columns and drop_missing_columns=True must restore the
|
||||
exact (metric, category_val) MultiIndex keys when all values for that metric
|
||||
are NaN. The restored keys must use the actual category values from the input
|
||||
data so that downstream rename/rolling validation and flatten produce the
|
||||
correct column names.
|
||||
"""
|
||||
mock_df = DataFrame(
|
||||
{
|
||||
"dttm": to_datetime(["2019-01-01", "2019-01-01"]),
|
||||
"category": ["A", "B"],
|
||||
"metric": [np.nan, np.nan],
|
||||
}
|
||||
)
|
||||
|
||||
df = pivot(
|
||||
df=mock_df,
|
||||
index=["dttm"],
|
||||
columns=["category"],
|
||||
aggregates={"metric": {"operator": "mean"}},
|
||||
drop_missing_columns=True,
|
||||
)
|
||||
|
||||
assert isinstance(df.columns, pd.MultiIndex)
|
||||
assert "metric" in df.columns.get_level_values(0)
|
||||
# Exact keys must reflect the real category values, not placeholders.
|
||||
assert ("metric", "A") in df.columns
|
||||
assert ("metric", "B") in df.columns
|
||||
|
||||
df = flatten(df)
|
||||
assert "metric, A" in df.columns
|
||||
assert "metric, B" in df.columns
|
||||
assert df["metric, A"].isna().all()
|
||||
assert df["metric, B"].isna().all()
|
||||
|
||||
|
||||
def test_pivot_preserves_all_nan_metric_multi_column():
|
||||
"""
|
||||
Pivot with multiple groupby columns and an all-NaN metric restores the full
|
||||
multi-level (metric, col_val_1, col_val_2) key, not a truncated or placeholder
|
||||
version. Exercises the case where columns=["country", "category"].
|
||||
"""
|
||||
mock_df = DataFrame(
|
||||
{
|
||||
"dttm": to_datetime(
|
||||
["2019-01-01", "2019-01-01", "2019-01-01", "2019-01-01"]
|
||||
),
|
||||
"country": ["US", "US", "EU", "EU"],
|
||||
"category": ["A", "B", "A", "B"],
|
||||
"metric": [np.nan, np.nan, np.nan, np.nan],
|
||||
}
|
||||
)
|
||||
|
||||
df = pivot(
|
||||
df=mock_df,
|
||||
index=["dttm"],
|
||||
columns=["country", "category"],
|
||||
aggregates={"metric": {"operator": "mean"}},
|
||||
drop_missing_columns=True,
|
||||
)
|
||||
|
||||
assert isinstance(df.columns, pd.MultiIndex)
|
||||
assert "metric" in df.columns.get_level_values(0)
|
||||
# All four combinations must be restored with correct full tuple keys.
|
||||
assert ("metric", "US", "A") in df.columns
|
||||
assert ("metric", "US", "B") in df.columns
|
||||
assert ("metric", "EU", "A") in df.columns
|
||||
assert ("metric", "EU", "B") in df.columns
|
||||
|
||||
df = flatten(df)
|
||||
assert "metric, US, A" in df.columns
|
||||
assert "metric, EU, B" in df.columns
|
||||
assert df["metric, US, A"].isna().all()
|
||||
|
||||
|
||||
def test_pivot_restored_nan_metric_column_order_is_deterministic():
|
||||
"""
|
||||
Restored all-NaN metric columns must appear in data-insertion order, not
|
||||
in nondeterministic hash-set iteration order. This prevents column ordering
|
||||
from varying across Python processes (which randomize hash seeds by default).
|
||||
"""
|
||||
mock_df = DataFrame(
|
||||
{
|
||||
"dttm": to_datetime(["2019-01-01", "2019-01-01", "2019-01-01"]),
|
||||
"category": ["C", "A", "B"],
|
||||
"metric": [np.nan, np.nan, np.nan],
|
||||
}
|
||||
)
|
||||
|
||||
df = pivot(
|
||||
df=mock_df,
|
||||
index=["dttm"],
|
||||
columns=["category"],
|
||||
aggregates={"metric": {"operator": "mean"}},
|
||||
drop_missing_columns=True,
|
||||
)
|
||||
|
||||
# Columns restored in data-insertion order: C, A, B (not alphabetical or random).
|
||||
assert list(df.columns.get_level_values(1)) == ["C", "A", "B"]
|
||||
|
||||
|
||||
def test_pivot_preserves_all_nan_metric_combine_value_with_metric():
|
||||
"""
|
||||
When combine_value_with_metric=True, a stack()/unstack() is applied after
|
||||
column restoration. stack() drops all-NaN rows by default, which would remove
|
||||
the restored metric before downstream post-processing can reference it.
|
||||
Using dropna=False on stack() ensures restored all-NaN metrics survive.
|
||||
"""
|
||||
mock_df = DataFrame(
|
||||
{
|
||||
"dttm": to_datetime(["2019-01-01", "2019-01-01"]),
|
||||
"category": ["A", "B"],
|
||||
"metric": [np.nan, np.nan],
|
||||
"metric2": [1.0, 2.0],
|
||||
}
|
||||
)
|
||||
|
||||
df = pivot(
|
||||
df=mock_df,
|
||||
index=["dttm"],
|
||||
columns=["category"],
|
||||
aggregates={
|
||||
"metric": {"operator": "mean"},
|
||||
"metric2": {"operator": "mean"},
|
||||
},
|
||||
drop_missing_columns=True,
|
||||
combine_value_with_metric=True,
|
||||
)
|
||||
|
||||
# After stack()/unstack(), columns are (category_val, metric_name) tuples.
|
||||
# The all-NaN metric must appear in level 1 alongside metric2.
|
||||
assert isinstance(df.columns, pd.MultiIndex)
|
||||
metric_names = df.columns.get_level_values(1).tolist()
|
||||
assert "metric" in metric_names
|
||||
assert "metric2" in metric_names
|
||||
|
||||
|
||||
def test_pivot_combine_sparse_metrics_no_spurious_extra_columns():
|
||||
"""
|
||||
With drop_missing_columns=True and combine_value_with_metric=True, using
|
||||
stack(dropna=False) to preserve restored all-NaN metrics must not alter output
|
||||
shape for sparse-but-not-all-NaN metric/category pairs. stack(dropna=False) only
|
||||
changes behaviour for rows that are entirely NaN (a restored metric); sparse rows
|
||||
with at least one non-NaN value are unaffected — same result as dropna=True.
|
||||
"""
|
||||
mock_df = DataFrame(
|
||||
{
|
||||
"dttm": to_datetime(["2019-01-01", "2019-01-01"]),
|
||||
"category": ["A", "B"],
|
||||
"metric1": [1.0, np.nan], # data only for category A
|
||||
"metric2": [np.nan, 2.0], # data only for category B
|
||||
}
|
||||
)
|
||||
|
||||
df = pivot(
|
||||
df=mock_df,
|
||||
index=["dttm"],
|
||||
columns=["category"],
|
||||
aggregates={
|
||||
"metric1": {"operator": "mean"},
|
||||
"metric2": {"operator": "mean"},
|
||||
},
|
||||
drop_missing_columns=True,
|
||||
combine_value_with_metric=True,
|
||||
)
|
||||
|
||||
# After combine, columns are (category_val, metric_name) tuples.
|
||||
# Neither metric is entirely absent after pivoting, so _restore adds nothing.
|
||||
# stack(dropna=False) does not change results for sparse rows with mixed NaN/data.
|
||||
assert isinstance(df.columns, pd.MultiIndex)
|
||||
assert sorted(df.columns.get_level_values(0).unique()) == ["A", "B"]
|
||||
assert sorted(df.columns.get_level_values(1).unique()) == ["metric1", "metric2"]
|
||||
# Sparse NaN cells are present but the data cells must retain their values.
|
||||
assert df[("A", "metric1")].iloc[0] == 1.0
|
||||
assert df[("B", "metric2")].iloc[0] == 2.0
|
||||
|
||||
|
||||
def test_pivot_only_entirely_absent_metrics_are_restored():
|
||||
"""
|
||||
Only metrics with zero surviving columns after pivoting are restored.
|
||||
A metric with partial NaN — data for some categories but not all — must not
|
||||
be touched: its present columns are unchanged and its absent sparse combinations
|
||||
remain dropped. This makes the restoration invariant explicit.
|
||||
"""
|
||||
mock_df = DataFrame(
|
||||
{
|
||||
"dttm": to_datetime(["2019-01-01", "2019-01-01"]),
|
||||
"category": ["A", "B"],
|
||||
"metric_all_nan": [np.nan, np.nan], # entirely absent → restored
|
||||
"metric_partial": [1.0, np.nan], # partially present → not restored
|
||||
}
|
||||
)
|
||||
|
||||
df = pivot(
|
||||
df=mock_df,
|
||||
index=["dttm"],
|
||||
columns=["category"],
|
||||
aggregates={
|
||||
"metric_all_nan": {"operator": "mean"},
|
||||
"metric_partial": {"operator": "mean"},
|
||||
},
|
||||
drop_missing_columns=True,
|
||||
)
|
||||
|
||||
# metric_all_nan was entirely absent: both category columns are restored as NaN.
|
||||
assert ("metric_all_nan", "A") in df.columns
|
||||
assert ("metric_all_nan", "B") in df.columns
|
||||
assert df[("metric_all_nan", "A")].isna().all()
|
||||
assert df[("metric_all_nan", "B")].isna().all()
|
||||
|
||||
# metric_partial has data for A: present column is unchanged, sparse B dropped.
|
||||
assert ("metric_partial", "A") in df.columns
|
||||
assert ("metric_partial", "B") not in df.columns
|
||||
assert df[("metric_partial", "A")].iloc[0] == 1.0
|
||||
|
||||
Reference in New Issue
Block a user