mirror of
https://github.com/apache/superset.git
synced 2026-04-30 21:44:40 +00:00
Compare commits
123 Commits
sidecar
...
docs/testi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d2c332165 | ||
|
|
572f3392d7 | ||
|
|
90f281f585 | ||
|
|
d62249d13f | ||
|
|
ff102aadb3 | ||
|
|
82e2bc6181 | ||
|
|
784ff82847 | ||
|
|
027b25e6b8 | ||
|
|
b652fab042 | ||
|
|
77a5969dc1 | ||
|
|
fb9032c05c | ||
|
|
7a9dbfe879 | ||
|
|
0de78d8203 | ||
|
|
abc2d46fed | ||
|
|
927cc1cda1 | ||
|
|
7f3840557a | ||
|
|
0defcb604b | ||
|
|
94686ddfbe | ||
|
|
ec322dfd8d | ||
|
|
cb88d886c7 | ||
|
|
608e3baf43 | ||
|
|
b6f6b75348 | ||
|
|
a5ad1d186c | ||
|
|
db88d80b3f | ||
|
|
4b71adaa9c | ||
|
|
5fbda3af40 | ||
|
|
bc0c40c80e | ||
|
|
f030d658c5 | ||
|
|
e85337c543 | ||
|
|
fe7f8062f3 | ||
|
|
dce74014da | ||
|
|
619b341cad | ||
|
|
9b6876be62 | ||
|
|
c601341520 | ||
|
|
78faaee685 | ||
|
|
4027bad1d6 | ||
|
|
ce55cc7dd7 | ||
|
|
48e1b1ff2c | ||
|
|
5ec8f9d886 | ||
|
|
ecb3ac68ff | ||
|
|
076e477fd4 | ||
|
|
1e4bc6ee78 | ||
|
|
db178cf527 | ||
|
|
5901320933 | ||
|
|
23bb4f88c0 | ||
|
|
4130b92966 | ||
|
|
38297edc6b | ||
|
|
0c8f326258 | ||
|
|
127f6b3d66 | ||
|
|
ea519a77b5 | ||
|
|
6cb3ef9f5d | ||
|
|
a889ae75fc | ||
|
|
b60be9655f | ||
|
|
fd6da21ce0 | ||
|
|
1bf112a57a | ||
|
|
1f530d45cb | ||
|
|
1187902e68 | ||
|
|
ad3eff9e90 | ||
|
|
3e554674ff | ||
|
|
dced2f8564 | ||
|
|
05c6a1bf20 | ||
|
|
c193d6d6a1 | ||
|
|
fb840b8e71 | ||
|
|
d0cc6f115b | ||
|
|
966e231f94 | ||
|
|
a66737cb05 | ||
|
|
bc6859a99d | ||
|
|
133e686224 | ||
|
|
7d0a472d1e | ||
|
|
c2534f9155 | ||
|
|
088ecdd0bf | ||
|
|
e1a2e4843a | ||
|
|
15e8ffee1e | ||
|
|
19ddcb7e5c | ||
|
|
36daa2dc3f | ||
|
|
7fd5a7668b | ||
|
|
95333e34b1 | ||
|
|
a9fb853e3e | ||
|
|
dea9068647 | ||
|
|
3416bd1479 | ||
|
|
e729b2dbb4 | ||
|
|
06261f262b | ||
|
|
454ed1883f | ||
|
|
b42060c880 | ||
|
|
7bf16d805d | ||
|
|
529adebe1b | ||
|
|
eb4351af83 | ||
|
|
5a2411fa64 | ||
|
|
078c1701f4 | ||
|
|
a7d349a5c6 | ||
|
|
7a20a65a4d | ||
|
|
912ed2ba80 | ||
|
|
fedb3ca941 | ||
|
|
42b15b6840 | ||
|
|
2f64343186 | ||
|
|
65376c7baf | ||
|
|
4c2b27e7f0 | ||
|
|
15e4e8df94 | ||
|
|
c5f220a9ff | ||
|
|
b05def1a8a | ||
|
|
da7f6efea8 | ||
|
|
1c2b9db4f0 | ||
|
|
0fce5ecfa5 | ||
|
|
385471c34d | ||
|
|
bef1f4d045 | ||
|
|
5a3182ce21 | ||
|
|
9efb80dbf4 | ||
|
|
a20b236809 | ||
|
|
4e969d19d1 | ||
|
|
876257fb94 | ||
|
|
472e599f91 | ||
|
|
d826e90395 | ||
|
|
c65cb284e6 | ||
|
|
bc54b7970a | ||
|
|
ce74ae095d | ||
|
|
9424538bb1 | ||
|
|
031fb4b5a8 | ||
|
|
7fb7ac8bef | ||
|
|
569a7b33a5 | ||
|
|
59df0d6f15 | ||
|
|
2e4ccffc11 | ||
|
|
2e51d02806 | ||
|
|
8406a827dd |
10
.claude/commands/js-to-ts.md
Normal file
10
.claude/commands/js-to-ts.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# JavaScript to TypeScript Migration Command
|
||||
|
||||
## Usage
|
||||
```
|
||||
/js-to-ts <core-filename>
|
||||
```
|
||||
- `<core-filename>` - Path to CORE file relative to `superset-frontend/` (e.g., `src/utils/common.js`, `src/middleware/loggerMiddleware.js`)
|
||||
|
||||
## Agent Instructions
|
||||
**See:** [../projects/js-to-ts/AGENT.md](../projects/js-to-ts/AGENT.md) for complete migration guide.
|
||||
684
.claude/projects/js-to-ts/AGENT.md
Normal file
684
.claude/projects/js-to-ts/AGENT.md
Normal file
@@ -0,0 +1,684 @@
|
||||
# JavaScript to TypeScript Migration Agent Guide
|
||||
|
||||
**Complete technical reference for converting JavaScript/JSX files to TypeScript/TSX in Apache Superset frontend.**
|
||||
|
||||
**Agent Role:** Atomic migration unit - migrate the core file + ALL related tests/mocks as one cohesive unit. Use `git mv` to preserve history, NO `git commit`. NO global import changes. Report results upon completion.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Migration Principles
|
||||
|
||||
1. **Atomic migration units** - Core file + all related tests/mocks migrate together
|
||||
2. **Zero `any` types** - Use proper TypeScript throughout
|
||||
3. **Leverage existing types** - Reuse established definitions
|
||||
4. **Type inheritance** - Derivatives extend base component types
|
||||
5. **Strategic placement** - File types for maximum discoverability
|
||||
6. **Surgical improvements** - Enhance existing types during migration
|
||||
|
||||
---
|
||||
|
||||
## Step 0: Dependency Check (MANDATORY)
|
||||
|
||||
**Command:**
|
||||
```bash
|
||||
grep -E "from '\.\./.*\.jsx?'|from '\./.*\.jsx?'|from 'src/.*\.jsx?'" superset-frontend/{filename}
|
||||
```
|
||||
|
||||
**Decision:**
|
||||
- ✅ No matches → Proceed with atomic migration (core + tests + mocks)
|
||||
- ❌ Matches found → EXIT with dependency report (see format below)
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Identify Related Files (REQUIRED)
|
||||
|
||||
**Atomic Migration Scope:**
|
||||
For core file `src/utils/example.js`, also migrate:
|
||||
- `src/utils/example.test.js` / `src/utils/example.test.jsx`
|
||||
- `src/utils/example.spec.js` / `src/utils/example.spec.jsx`
|
||||
- `src/utils/__mocks__/example.js`
|
||||
- Any other related test/mock files found by pattern matching
|
||||
|
||||
**Find all related test and mock files:**
|
||||
```bash
|
||||
# Pattern-based search for related files
|
||||
basename=$(basename {filename} .js)
|
||||
dirname=$(dirname superset-frontend/{filename})
|
||||
|
||||
# Find test files
|
||||
find "$dirname" -name "${basename}.test.js" -o -name "${basename}.test.jsx"
|
||||
find "$dirname" -name "${basename}.spec.js" -o -name "${basename}.spec.jsx"
|
||||
|
||||
# Find mock files
|
||||
find "$dirname" -name "__mocks__/${basename}.js"
|
||||
find "$dirname" -name "${basename}.mock.js"
|
||||
```
|
||||
|
||||
**Migration Requirement:** All discovered related files MUST be migrated together as one atomic unit.
|
||||
|
||||
**Test File Creation:** If NO test files exist for the core file, CREATE a minimal test file using the following pattern:
|
||||
- Location: Same directory as core file
|
||||
- Name: `{basename}.test.ts` (e.g., `DebouncedMessageQueue.test.ts`)
|
||||
- Content: Basic test structure importing and testing the main functionality
|
||||
- Use proper TypeScript types in test file
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Type Reference Map
|
||||
|
||||
### From `@superset-ui/core`
|
||||
```typescript
|
||||
// Data & Query
|
||||
QueryFormData, QueryData, JsonObject, AnnotationData, AdhocMetric
|
||||
LatestQueryFormData, GenericDataType, DatasourceType, ExtraFormData
|
||||
DataMaskStateWithId, NativeFilterScope, NativeFiltersState, NativeFilterTarget
|
||||
|
||||
// UI & Theme
|
||||
FeatureFlagMap, LanguagePack, ColorSchemeConfig, SequentialSchemeConfig
|
||||
```
|
||||
|
||||
### From `@superset-ui/chart-controls`
|
||||
```typescript
|
||||
Dataset, ColumnMeta, ControlStateMapping
|
||||
```
|
||||
|
||||
### From Local Types (`src/types/`)
|
||||
```typescript
|
||||
// Authentication
|
||||
User, UserWithPermissionsAndRoles, BootstrapUser, PermissionsAndRoles
|
||||
|
||||
// Dashboard
|
||||
Dashboard, DashboardState, DashboardInfo, DashboardLayout, LayoutItem
|
||||
ComponentType, ChartConfiguration, ActiveFilters
|
||||
|
||||
// Charts
|
||||
Chart, ChartState, ChartStatus, ChartLinkedDashboard, Slice, SaveActionType
|
||||
|
||||
// Data
|
||||
Datasource, Database, Owner, Role
|
||||
|
||||
// UI Components
|
||||
TagType, FavoriteStatus, Filter, ImportResourceName
|
||||
```
|
||||
|
||||
### From Domain Types
|
||||
```typescript
|
||||
// src/dashboard/types.ts
|
||||
RootState, ChartsState, DatasourcesState, FilterBarOrientation
|
||||
ChartCrossFiltersConfig, ActiveTabs, MenuKeys
|
||||
|
||||
// src/explore/types.ts
|
||||
ExplorePageInitialData, ExplorePageState, ExploreResponsePayload, OptionSortType
|
||||
|
||||
// src/SqlLab/types.ts
|
||||
[SQL Lab specific types]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Type Organization Strategy
|
||||
|
||||
### Type Placement Hierarchy
|
||||
|
||||
1. **Component-Colocated** (90% of cases)
|
||||
```typescript
|
||||
// Same file as component
|
||||
interface MyComponentProps {
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Feature-Shared**
|
||||
```typescript
|
||||
// src/[domain]/components/[Feature]/types.ts
|
||||
export interface FilterConfiguration {
|
||||
filterId: string;
|
||||
targets: NativeFilterTarget[];
|
||||
}
|
||||
```
|
||||
|
||||
3. **Domain-Wide**
|
||||
```typescript
|
||||
// src/[domain]/types.ts
|
||||
export interface ExploreFormData extends QueryFormData {
|
||||
viz_type: string;
|
||||
}
|
||||
```
|
||||
|
||||
4. **Global**
|
||||
```typescript
|
||||
// src/types/[TypeName].ts
|
||||
export interface ApiResponse<T> {
|
||||
result: T;
|
||||
count?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Type Discovery Commands
|
||||
```bash
|
||||
# Search existing types before creating
|
||||
find superset-frontend/src -name "types.ts" -exec grep -l "[TypeConcept]" {} \;
|
||||
grep -r "interface.*Props\|type.*Props" superset-frontend/src/
|
||||
```
|
||||
|
||||
### Derivative Component Patterns
|
||||
|
||||
**Rule:** Components that extend others should extend their type interfaces.
|
||||
|
||||
```typescript
|
||||
// ✅ Base component type
|
||||
interface SelectProps {
|
||||
value: string | number;
|
||||
options: SelectOption[];
|
||||
onChange: (value: string | number) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// ✅ Derivative extends base
|
||||
interface ChartSelectProps extends SelectProps {
|
||||
charts: Chart[];
|
||||
onChartSelect: (chart: Chart) => void;
|
||||
}
|
||||
|
||||
// ✅ Derivative with modified props
|
||||
interface DatabaseSelectProps extends Omit<SelectProps, 'value' | 'onChange'> {
|
||||
value: number; // Narrowed type
|
||||
onChange: (databaseId: number) => void; // Specific signature
|
||||
}
|
||||
```
|
||||
|
||||
**Common Patterns:**
|
||||
- **Extension:** `extends BaseProps` - adds new props
|
||||
- **Omission:** `Omit<BaseProps, 'prop'>` - removes props
|
||||
- **Modification:** `Omit<BaseProps, 'prop'> & { prop: NewType }` - changes prop type
|
||||
- **Restriction:** Override with narrower types (union → specific)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Migration Recipe
|
||||
|
||||
### Step 2: File Conversion
|
||||
```bash
|
||||
# Use git mv to preserve history
|
||||
git mv component.js component.ts
|
||||
git mv Component.jsx Component.tsx
|
||||
```
|
||||
|
||||
### Step 3: Import & Type Setup
|
||||
```typescript
|
||||
// Import order (enforced by linting)
|
||||
import { FC, ReactNode } from 'react';
|
||||
import { JsonObject, QueryFormData } from '@superset-ui/core';
|
||||
import { Dataset } from '@superset-ui/chart-controls';
|
||||
import type { Dashboard } from 'src/types/Dashboard';
|
||||
```
|
||||
|
||||
### Step 4: Function & Component Typing
|
||||
```typescript
|
||||
// Functions with proper parameter/return types
|
||||
export function processData(
|
||||
data: Dataset[],
|
||||
config: JsonObject
|
||||
): ProcessedData[] {
|
||||
// implementation
|
||||
}
|
||||
|
||||
// Component props with inheritance
|
||||
interface ComponentProps extends BaseProps {
|
||||
data: Chart[];
|
||||
onSelect: (id: number) => void;
|
||||
}
|
||||
|
||||
const Component: FC<ComponentProps> = ({ data, onSelect }) => {
|
||||
// implementation
|
||||
};
|
||||
```
|
||||
|
||||
### Step 5: State & Redux Typing
|
||||
```typescript
|
||||
// Hooks with specific types
|
||||
const [data, setData] = useState<Chart[]>([]);
|
||||
const [selected, setSelected] = useState<number | null>(null);
|
||||
|
||||
// Redux with existing RootState
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
charts: state.charts,
|
||||
user: state.user,
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Type Debugging Strategies (Real-World Learnings)
|
||||
|
||||
### The Evolution of Type Approaches
|
||||
When you hit type errors, follow this debugging evolution:
|
||||
|
||||
#### 1. ❌ Idealized Union Types (First Attempt)
|
||||
```typescript
|
||||
// Looks clean but doesn't match reality
|
||||
type DatasourceInput = Datasource | QueryEditor;
|
||||
```
|
||||
**Problem**: Real calling sites pass variations, not exact types.
|
||||
|
||||
#### 2. ❌ Overly Precise Types (Second Attempt)
|
||||
```typescript
|
||||
// Tried to match exact calling signatures
|
||||
type DatasourceInput =
|
||||
| IDatasource // From DatasourcePanel
|
||||
| (QueryEditor & { columns: ColumnMeta[] }); // From SaveQuery
|
||||
```
|
||||
**Problem**: Too rigid, doesn't handle legacy variations.
|
||||
|
||||
#### 3. ✅ Flexible Interface (Final Solution)
|
||||
```typescript
|
||||
// Captures what the function actually needs
|
||||
interface DatasourceInput {
|
||||
name?: string | null; // Allow null for compatibility
|
||||
datasource_name?: string | null; // Legacy variations
|
||||
columns?: any[]; // Multiple column types accepted
|
||||
database?: { id?: number };
|
||||
// ... other optional properties
|
||||
}
|
||||
```
|
||||
**Success**: Works with all calling sites, focuses on function needs.
|
||||
|
||||
### Type Debugging Process
|
||||
1. **Start with compilation errors** - they show exact mismatches
|
||||
2. **Examine actual usage** - look at calling sites, not idealized types
|
||||
3. **Build flexible interfaces** - capture what functions need, not rigid contracts
|
||||
4. **Iterate based on downstream validation** - let calling sites guide your types
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Anti-Patterns to Avoid
|
||||
|
||||
```typescript
|
||||
// ❌ Never use any
|
||||
const obj: any = {};
|
||||
|
||||
// ✅ Use proper types
|
||||
const obj: Record<string, JsonObject> = {};
|
||||
|
||||
// ❌ Don't recreate base component props
|
||||
interface ChartSelectProps {
|
||||
value: string; // Duplicated from SelectProps
|
||||
onChange: () => void; // Duplicated from SelectProps
|
||||
charts: Chart[]; // New prop
|
||||
}
|
||||
|
||||
// ✅ Inherit and extend
|
||||
interface ChartSelectProps extends SelectProps {
|
||||
charts: Chart[]; // Only new props
|
||||
}
|
||||
|
||||
// ❌ Don't create ad-hoc type variations
|
||||
interface UserInfo {
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
// ✅ Extend existing types (DRY principle)
|
||||
import { User } from 'src/types/bootstrapTypes';
|
||||
type UserDisplayInfo = Pick<User, 'firstName' | 'lastName' | 'email'>;
|
||||
|
||||
// ❌ Don't create overly rigid unions
|
||||
type StrictInput = ExactTypeA | ExactTypeB;
|
||||
|
||||
// ✅ Create flexible interfaces for function parameters
|
||||
interface FlexibleInput {
|
||||
// Focus on what the function actually needs
|
||||
commonProperty: string;
|
||||
optionalVariations?: any; // Allow for legacy variations
|
||||
}
|
||||
```
|
||||
|
||||
## 📍 DRY Type Guidelines (WHERE TYPES BELONG)
|
||||
|
||||
### Type Placement Rules
|
||||
**CRITICAL**: Type variations must live close to where they belong, not scattered across files.
|
||||
|
||||
#### ✅ Proper Type Organization
|
||||
```typescript
|
||||
// ❌ Don't create one-off interfaces in utility files
|
||||
// src/utils/datasourceUtils.ts
|
||||
interface DatasourceInput { /* custom interface */ } // Wrong!
|
||||
|
||||
// ✅ Use existing types or extend them in their proper domain
|
||||
// src/utils/datasourceUtils.ts
|
||||
import { IDatasource } from 'src/explore/components/DatasourcePanel';
|
||||
import { QueryEditor } from 'src/SqlLab/types';
|
||||
|
||||
// Create flexible interface that references existing types
|
||||
interface FlexibleDatasourceInput {
|
||||
// Properties that actually exist across variations
|
||||
}
|
||||
```
|
||||
|
||||
#### Type Location Hierarchy
|
||||
1. **Domain Types**: `src/{domain}/types.ts` (dashboard, explore, SqlLab)
|
||||
2. **Component Types**: Co-located with components
|
||||
3. **Global Types**: `src/types/` directory
|
||||
4. **Utility Types**: Only when they truly don't belong elsewhere
|
||||
|
||||
#### ✅ DRY Type Patterns
|
||||
```typescript
|
||||
// ✅ Extend existing domain types
|
||||
interface SaveQueryData extends Pick<QueryEditor, 'sql' | 'dbId' | 'catalog'> {
|
||||
columns: ColumnMeta[]; // Add what's needed
|
||||
}
|
||||
|
||||
// ✅ Create flexible interfaces for cross-domain utilities
|
||||
interface CrossDomainInput {
|
||||
// Common properties that exist across different source types
|
||||
name?: string | null; // Accommodate legacy null values
|
||||
// Only include properties the function actually uses
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 PropTypes Auto-Generation (Elegant Approach)
|
||||
|
||||
**IMPORTANT**: Superset has `babel-plugin-typescript-to-proptypes` configured to automatically generate PropTypes from TypeScript interfaces. Use this instead of manual PropTypes duplication!
|
||||
|
||||
### ❌ Manual PropTypes Duplication (Avoid This)
|
||||
```typescript
|
||||
export interface MyComponentProps {
|
||||
title: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
// 8+ lines of manual PropTypes duplication 😱
|
||||
const propTypes = PropTypes.shape({
|
||||
title: PropTypes.string.isRequired,
|
||||
count: PropTypes.number,
|
||||
});
|
||||
|
||||
export default propTypes;
|
||||
```
|
||||
|
||||
### ✅ Auto-Generated PropTypes (Use This)
|
||||
```typescript
|
||||
import { InferProps } from 'prop-types';
|
||||
|
||||
export interface MyComponentProps {
|
||||
title: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
// Single validator function - babel plugin auto-generates PropTypes! ✨
|
||||
export default function MyComponentValidator(props: MyComponentProps) {
|
||||
return null; // PropTypes auto-assigned by babel-plugin-typescript-to-proptypes
|
||||
}
|
||||
|
||||
// Optional: For consumers needing PropTypes type inference
|
||||
export type MyComponentPropsInferred = InferProps<typeof MyComponentValidator>;
|
||||
```
|
||||
|
||||
### Migration Pattern for Type-Only Files
|
||||
|
||||
**When migrating type-only files with manual PropTypes:**
|
||||
|
||||
1. **Keep the TypeScript interfaces** (single source of truth)
|
||||
2. **Replace manual PropTypes** with validator function
|
||||
3. **Remove PropTypes imports** and manual shape definitions
|
||||
4. **Add InferProps import** if type inference needed
|
||||
|
||||
**Example Migration:**
|
||||
```typescript
|
||||
// Before: 25+ lines with manual PropTypes duplication
|
||||
export interface AdhocFilterType { /* ... */ }
|
||||
const adhocFilterTypePropTypes = PropTypes.oneOfType([...]);
|
||||
|
||||
// After: 3 lines with auto-generation
|
||||
export interface AdhocFilterType { /* ... */ }
|
||||
export default function AdhocFilterValidator(props: { filter: AdhocFilterType }) {
|
||||
return null; // Auto-generated PropTypes by babel plugin
|
||||
}
|
||||
```
|
||||
|
||||
### Component PropTypes Pattern
|
||||
|
||||
**For React components, the babel plugin works automatically:**
|
||||
|
||||
```typescript
|
||||
interface ComponentProps {
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const MyComponent: FC<ComponentProps> = ({ title, onClick }) => {
|
||||
// Component implementation
|
||||
};
|
||||
|
||||
// PropTypes automatically generated by babel plugin - no manual work needed!
|
||||
export default MyComponent;
|
||||
```
|
||||
|
||||
### Auto-Generation Benefits
|
||||
|
||||
- ✅ **Single source of truth**: TypeScript interfaces drive PropTypes
|
||||
- ✅ **No duplication**: Eliminate 15-20 lines of manual PropTypes code
|
||||
- ✅ **Automatic updates**: Changes to TypeScript automatically update PropTypes
|
||||
- ✅ **Type safety**: Compile-time checking ensures PropTypes match interfaces
|
||||
- ✅ **Backward compatibility**: Existing JavaScript components continue working
|
||||
|
||||
### Babel Plugin Configuration
|
||||
|
||||
The plugin is already configured in `babel.config.js`:
|
||||
```javascript
|
||||
['babel-plugin-typescript-to-proptypes', { loose: true }]
|
||||
```
|
||||
|
||||
**No additional setup required** - just use TypeScript interfaces and the plugin handles the rest!
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test File Migration Patterns
|
||||
|
||||
### Test File Priority
|
||||
- **Always migrate test files** alongside production files
|
||||
- **Test files are often leaf nodes** - good starting candidates
|
||||
- **Create tests if missing** - Leverage new TypeScript types for better test coverage
|
||||
|
||||
### Test-Specific Type Patterns
|
||||
```typescript
|
||||
// Mock interfaces for testing
|
||||
interface MockStore {
|
||||
getState: () => Partial<RootState>; // Partial allows minimal mocking
|
||||
}
|
||||
|
||||
// Type-safe mocking for complex objects
|
||||
const mockDashboardInfo: Partial<DashboardInfo> as DashboardInfo = {
|
||||
id: 123,
|
||||
json_metadata: '{}',
|
||||
};
|
||||
|
||||
// Sinon stub typing
|
||||
let postStub: sinon.SinonStub;
|
||||
beforeEach(() => {
|
||||
postStub = sinon.stub(SupersetClient, 'post');
|
||||
});
|
||||
|
||||
// Use stub reference instead of original method
|
||||
expect(postStub.callCount).toBe(1);
|
||||
expect(postStub.getCall(0).args[0].endpoint).toMatch('/api/');
|
||||
```
|
||||
|
||||
### Test Migration Recipe
|
||||
1. **Migrate production file first** (if both need migration)
|
||||
2. **Update test imports** to point to `.ts/.tsx` files
|
||||
3. **Add proper mock typing** using `Partial<T> as T` pattern
|
||||
4. **Fix stub typing** - Use stub references, not original methods
|
||||
5. **Verify all tests pass** with TypeScript compilation
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Type Conflict Resolution
|
||||
|
||||
### Multiple Type Definitions Issue
|
||||
**Problem**: Same type name defined in multiple files causes compilation errors.
|
||||
|
||||
**Example**: `DashboardInfo` defined in both:
|
||||
- `src/dashboard/reducers/types.ts` (minimal)
|
||||
- `src/dashboard/components/Header/types.ts` (different shape)
|
||||
- `src/dashboard/types.ts` (complete - used by RootState)
|
||||
|
||||
### Resolution Strategy
|
||||
1. **Identify the authoritative type**:
|
||||
```bash
|
||||
# Find which type is used by RootState/main interfaces
|
||||
grep -r "DashboardInfo" src/dashboard/types.ts
|
||||
```
|
||||
|
||||
2. **Use import from authoritative source**:
|
||||
```typescript
|
||||
// ✅ Import from main domain types
|
||||
import { RootState, DashboardInfo } from 'src/dashboard/types';
|
||||
|
||||
// ❌ Don't import from component-specific files
|
||||
import { DashboardInfo } from 'src/dashboard/components/Header/types';
|
||||
```
|
||||
|
||||
3. **Mock complex types in tests**:
|
||||
```typescript
|
||||
// For testing - provide minimal required fields
|
||||
const mockInfo: Partial<DashboardInfo> as DashboardInfo = {
|
||||
id: 123,
|
||||
json_metadata: '{}',
|
||||
// Only provide fields actually used in test
|
||||
};
|
||||
```
|
||||
|
||||
### Type Hierarchy Discovery Commands
|
||||
```bash
|
||||
# Find all definitions of a type
|
||||
grep -r "interface.*TypeName\|type.*TypeName" src/
|
||||
|
||||
# Find import usage patterns
|
||||
grep -r "import.*TypeName" src/
|
||||
|
||||
# Check what RootState uses
|
||||
grep -A 10 -B 10 "TypeName" src/*/types.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agent Constraints (CRITICAL)
|
||||
|
||||
1. **Use git mv** - Run `git mv file.js file.ts` to preserve git history, but NO `git commit`
|
||||
2. **NO global import changes** - Don't update imports across codebase
|
||||
3. **Type files OK** - Can modify existing type files to improve/align types
|
||||
4. **Single-File TypeScript Validation** (CRITICAL) - tsc has known issues with multi-file compilation:
|
||||
- **Core Issue**: TypeScript's `tsc` has documented problems validating multiple files simultaneously in complex projects
|
||||
- **Solution**: ALWAYS validate files one at a time using individual `tsc` calls
|
||||
- **Command Pattern**: `cd superset-frontend && npx tscw --noEmit --allowJs --composite false --project tsconfig.json {single-file-path}`
|
||||
- **Why**: Multi-file validation can produce false positives, miss real errors, and conflict during parallel agent execution
|
||||
5. **Downstream Impact Validation** (CRITICAL) - Your migration affects calling sites:
|
||||
- **Find downstream files**: `find superset-frontend/src -name "*.tsx" -o -name "*.ts" | xargs grep -l "your-core-filename" 2>/dev/null || echo "No files found"`
|
||||
- **Validate each downstream file individually**: `cd superset-frontend && npx tscw --noEmit --allowJs --composite false --project tsconfig.json {each-downstream-file}`
|
||||
- **Fix type mismatches** you introduced in calling sites
|
||||
- **NEVER ignore downstream errors** - they indicate your types don't match reality
|
||||
6. **Avoid Project-Wide Validation During Migration**:
|
||||
- **NEVER use `npm run type`** during parallel agent execution - produces unreliable results
|
||||
- **Single-file validation is authoritative** - trust individual file checks over project-wide scans
|
||||
6. **ESLint validation** - Run `npm run eslint -- --fix {file}` for each migrated file to auto-fix formatting/linting issues
|
||||
6. Zero `any` types - use proper TypeScript types
|
||||
7. Search existing types before creating new ones
|
||||
8. Follow patterns from this guide
|
||||
|
||||
---
|
||||
|
||||
## Success Report Format
|
||||
|
||||
```
|
||||
SUCCESS: Atomic Migration of {core-filename}
|
||||
|
||||
## Files Migrated (Atomic Unit)
|
||||
- Core: {core-filename} → {core-filename.ts/tsx}
|
||||
- Tests: {list-of-test-files} → {list-of-test-files.ts/tsx} OR "CREATED: {basename}.test.ts"
|
||||
- Mocks: {list-of-mock-files} → {list-of-mock-files.ts}
|
||||
- Type files modified: {list-of-type-files}
|
||||
|
||||
## Types Created/Improved
|
||||
- {TypeName}: {location} ({scope}) - {rationale}
|
||||
- {ExistingType}: enhanced in {location} - {improvement-description}
|
||||
|
||||
## Documentation Recommendations
|
||||
- ADD_TO_DIRECTORY: {TypeName} - {reason}
|
||||
- NO_DOCUMENTATION: {TypeName} - {reason}
|
||||
|
||||
## Quality Validation
|
||||
- **Single-File TypeScript Validation**: ✅ PASS - Core files individually validated
|
||||
- Core file: `npx tscw --noEmit --allowJs --composite false --project tsconfig.json {core-file}`
|
||||
- Test files: `npx tscw --noEmit --allowJs --composite false --project tsconfig.json {test-file}` (if exists)
|
||||
- **Downstream Impact Check**: ✅ PASS - Found {N} files importing this module, all validate individually
|
||||
- Downstream files: {list-of-files-that-import-your-module}
|
||||
- Individual validation: `npx tscw --noEmit --allowJs --composite false --project tsconfig.json {each-downstream-file}`
|
||||
- **ESLint validation**: ✅ PASS (using `npm run eslint -- --fix {files}` to auto-fix formatting)
|
||||
- **Zero any types**: ✅ PASS
|
||||
- **Local imports resolved**: ✅ PASS
|
||||
- **Functionality preserved**: ✅ PASS
|
||||
- **Tests pass** (if test file): ✅ PASS
|
||||
- **Follow-up action required**: {YES/NO}
|
||||
|
||||
## Validation Strategy Notes
|
||||
- **Single-file approach used**: Avoided multi-file tsc validation due to known TypeScript compilation issues
|
||||
- **Project-wide validation skipped**: `npm run type` not used during parallel migration to prevent false positives
|
||||
|
||||
## Migration Learnings
|
||||
- Type conflicts encountered: {describe any multiple type definitions}
|
||||
- Mock patterns used: {describe test mocking approaches}
|
||||
- Import hierarchy decisions: {note authoritative type sources used}
|
||||
- PropTypes strategy: {AUTO_GENERATED via babel plugin | MANUAL_DUPLICATION_REMOVED | N/A}
|
||||
|
||||
## Improvement Suggestions for Documentation
|
||||
- AGENT.md enhancement: {suggest additions to migration guide}
|
||||
- Common pattern identified: {note reusable patterns for future migrations}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependency Block Report Format
|
||||
|
||||
```
|
||||
DEPENDENCY_BLOCK: Cannot migrate {filename}
|
||||
|
||||
## Blocking Dependencies
|
||||
- {path}: {type} - {usage} - {priority}
|
||||
|
||||
## Impact Analysis
|
||||
- Estimated types: {number}
|
||||
- Expected locations: {list}
|
||||
- Cross-domain: {YES/NO}
|
||||
|
||||
## Recommended Order
|
||||
{ordered-list}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Quick Reference
|
||||
|
||||
**Type Utilities:**
|
||||
- `Record<K, V>` - Object with specific key/value types
|
||||
- `Partial<T>` - All properties optional
|
||||
- `Pick<T, K>` - Subset of properties
|
||||
- `Omit<T, K>` - Exclude specific properties
|
||||
- `NonNullable<T>` - Exclude null/undefined
|
||||
|
||||
**Event Types:**
|
||||
- `MouseEvent<HTMLButtonElement>`
|
||||
- `ChangeEvent<HTMLInputElement>`
|
||||
- `FormEvent<HTMLFormElement>`
|
||||
|
||||
**React Types:**
|
||||
- `FC<Props>` - Functional component
|
||||
- `ReactNode` - Any renderable content
|
||||
- `CSSProperties` - Style objects
|
||||
|
||||
---
|
||||
|
||||
**Remember:** Every type should add value and clarity. The goal is meaningful type safety that catches bugs and improves developer experience.
|
||||
199
.claude/projects/js-to-ts/COORDINATOR.md
Normal file
199
.claude/projects/js-to-ts/COORDINATOR.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# JS-to-TS Coordinator Workflow
|
||||
|
||||
**Role:** Strategic migration coordination - select leaf-node files, trigger agents, review results, handle integration, manage dependencies.
|
||||
|
||||
---
|
||||
|
||||
## 1. Core File Selection Strategy
|
||||
|
||||
**Target ONLY Core Files**: Coordinators identify core files (production code), agents handle related tests/mocks atomically.
|
||||
|
||||
**File Analysis Commands**:
|
||||
```bash
|
||||
# Find CORE files with no JS/JSX dependencies (exclude tests/mocks) - SIZE PRIORITIZED
|
||||
find superset-frontend/src -name "*.js" -o -name "*.jsx" | grep -v "test\|spec\|mock" | xargs wc -l | sort -n | head -20
|
||||
|
||||
# Alternative: Get file sizes in lines with paths
|
||||
find superset-frontend/src -name "*.js" -o -name "*.jsx" | grep -v "test\|spec\|mock" | while read file; do
|
||||
lines=$(wc -l < "$file")
|
||||
echo "$lines $file"
|
||||
done | sort -n | head -20
|
||||
|
||||
# Check dependencies for core files only (start with smallest)
|
||||
for file in <core-files-sorted-by-size>; do
|
||||
echo "=== $file ($(wc -l < "$file") lines) ==="
|
||||
grep -E "from '\.\./.*\.jsx?'|from '\./.*\.jsx?'|from 'src/.*\.jsx?'" "$file" || echo "✅ LEAF CANDIDATE"
|
||||
done
|
||||
|
||||
# Identify heavily imported files (migrate last)
|
||||
grep -r "from.*utils/common" superset-frontend/src/ | wc -l
|
||||
|
||||
# Quick leaf analysis with size priority
|
||||
find superset-frontend/src -name "*.js" -o -name "*.jsx" | grep -v "test\|spec\|mock" | head -30 | while read file; do
|
||||
deps=$(grep -E "from '\.\./.*\.jsx?'|from '\./.*\.jsx?'|from 'src/.*\.jsx?'" "$file" | wc -l)
|
||||
lines=$(wc -l < "$file")
|
||||
if [ "$deps" -eq 0 ]; then
|
||||
echo "✅ LEAF: $lines lines - $file"
|
||||
fi
|
||||
done | sort -n
|
||||
```
|
||||
|
||||
**Priority Order** (Smallest files first for easier wins):
|
||||
1. **Small leaf files** (<50 lines) - No JS/JSX imports, quick TypeScript conversion
|
||||
2. **Medium leaf files** (50-200 lines) - Self-contained utilities and helpers
|
||||
3. **Small dependency files** (<100 lines) - Import only already-migrated files
|
||||
4. **Larger components** (200+ lines) - Complex but well-contained functionality
|
||||
5. **Core foundational files** (utils/common.js, controls.jsx) - migrate last regardless of size
|
||||
|
||||
**Size-First Benefits**:
|
||||
- Faster completion builds momentum
|
||||
- Earlier validation of migration patterns
|
||||
- Easier rollback if issues arise
|
||||
- Better success rate for agent learning
|
||||
|
||||
**Migration Unit**: Each agent call migrates:
|
||||
- 1 core file (primary target)
|
||||
- All related `*.test.js/jsx` files
|
||||
- All related `*.mock.js` files
|
||||
- All related `__mocks__/` files
|
||||
|
||||
---
|
||||
|
||||
## 2. Task Creation & Agent Control
|
||||
|
||||
### Task Triggering
|
||||
When triggering the `/js-to-ts` command:
|
||||
- **Task Title**: Use the core filename as the task title (e.g., "DebouncedMessageQueue.js migration", "hostNamesConfig.js migration")
|
||||
- **Task Description**: Include the full relative path to help agent locate the file
|
||||
- **Reference**: Point agent to [AGENT.md](./AGENT.md) for technical instructions
|
||||
|
||||
### Post-Processing Workflow
|
||||
After each agent completes:
|
||||
|
||||
1. **Review Agent Report**: Always read and analyze the complete agent report
|
||||
2. **Share Summary**: Provide user with key highlights from agent's work:
|
||||
- Files migrated (core + tests/mocks)
|
||||
- Types created or improved
|
||||
- Any validation issues or coordinator actions needed
|
||||
3. **Quality Assessment**: Evaluate agent's TypeScript implementation against criteria:
|
||||
- ✅ **Type Usage**: Proper types used, no `any` types
|
||||
- ✅ **Type Filing**: Types placed in correct hierarchy (component → feature → domain → global)
|
||||
- ✅ **Side Effects**: No unintended changes to other files
|
||||
- ✅ **Import Alignment**: Proper .ts/.tsx import extensions
|
||||
4. **Integration Decision**:
|
||||
- **COMMIT**: If agent work is complete and high quality
|
||||
- **FIX & COMMIT**: If minor issues need coordinator fixes
|
||||
- **ROLLBACK**: If major issues require complete rework
|
||||
5. **Next Action**: Ask user preference - commit this work or trigger next migration
|
||||
|
||||
---
|
||||
|
||||
## 3. Integration Decision Framework
|
||||
|
||||
**Automatic Integration** ✅:
|
||||
- `npm run type` passes without errors
|
||||
- Agent created clean TypeScript with proper types
|
||||
- Types appropriately filed in hierarchy
|
||||
|
||||
**Coordinator Integration** (Fix Side-Effects) 🔧:
|
||||
- `npm run type` fails BUT agent's work is high quality
|
||||
- Good type usage, proper patterns, well-organized
|
||||
- Side-effects are manageable TypeScript compilation errors
|
||||
- **Coordinator Action**: Integrate the change, then fix global compilation issues
|
||||
|
||||
**Rollback Only** ❌:
|
||||
- Agent introduced `any` types or poor type choices
|
||||
- Types poorly organized or conflicting with existing patterns
|
||||
- Fundamental approach issues requiring complete rework
|
||||
|
||||
**Integration Process**:
|
||||
1. **Review**: Agent already used `git mv` to preserve history
|
||||
2. **Fix Side-Effects**: Update dependent files with proper import extensions
|
||||
3. **Resolve Types**: Fix any cascading type issues across codebase
|
||||
4. **Validate**: Ensure `npm run type` passes after fixes
|
||||
|
||||
---
|
||||
|
||||
## 4. Common Integration Patterns
|
||||
|
||||
**Common Side-Effects (Expect These)**:
|
||||
- **Type import conflicts**: Multiple definitions of same type name
|
||||
- **Mock object typing**: Tests need complete type satisfaction
|
||||
- **Stub method references**: Use stub vars instead of original methods
|
||||
|
||||
**Coordinator Fixes (Standard Process)**:
|
||||
1. **Import Resolution**:
|
||||
```bash
|
||||
# Find authoritative type source
|
||||
grep -r "TypeName" src/*/types.ts
|
||||
# Import from domain types (src/dashboard/types.ts) not component types
|
||||
```
|
||||
|
||||
2. **Test Mock Completion**:
|
||||
```typescript
|
||||
// Use Partial<T> as T pattern for minimal mocking
|
||||
const mockDashboard: Partial<DashboardInfo> as DashboardInfo = {
|
||||
id: 123,
|
||||
json_metadata: '{}',
|
||||
};
|
||||
```
|
||||
|
||||
3. **Stub Reference Fixes**:
|
||||
```typescript
|
||||
// ✅ Use stub variable
|
||||
expect(postStub.callCount).toBe(1);
|
||||
// ❌ Don't use original method
|
||||
expect(SupersetClient.post.callCount).toBe(1);
|
||||
```
|
||||
|
||||
4. **Validation Commands**:
|
||||
```bash
|
||||
npm run type # TypeScript compilation
|
||||
npm test -- filename # Test functionality
|
||||
git status # Should show rename, not add/delete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. File Categories for Planning
|
||||
|
||||
### Leaf Files (Start Here)
|
||||
**Self-contained files with minimal JS/JSX dependencies**:
|
||||
- Test files (80 files) - Usually only import the file being tested
|
||||
- Utility files without internal dependencies
|
||||
- Components importing only external libraries
|
||||
|
||||
### Heavily Imported Files (Migrate Last)
|
||||
**Core files that many others depend on**:
|
||||
- `utils/common.js` - Core utility functions
|
||||
- `utils/reducerUtils.js` - Redux helpers
|
||||
- `@superset-ui/core` equivalent files
|
||||
- Major state management files (`explore/store.js`, `dashboard/actions/`)
|
||||
|
||||
### Complex Components (Middle Priority)
|
||||
**Large files requiring careful type analysis**:
|
||||
- `components/Datasource/DatasourceEditor.jsx` (1,809 lines)
|
||||
- `explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx` (1,031 lines)
|
||||
- `explore/components/ExploreViewContainer/index.jsx` (911 lines)
|
||||
|
||||
---
|
||||
|
||||
## 6. Success Metrics & Continuous Improvement
|
||||
|
||||
**Per-File Gates**:
|
||||
- ✅ `npm run type` passes after each migration
|
||||
- ✅ Zero `any` types introduced
|
||||
- ✅ All imports properly typed
|
||||
- ✅ Types filed in correct hierarchy
|
||||
|
||||
**Linear Scheduling**:
|
||||
When agents report `DEPENDENCY_BLOCK`:
|
||||
- Queue dependencies in linear order
|
||||
- Process one file at a time to avoid conflicts
|
||||
- Handle cascading type changes between files
|
||||
|
||||
**After Each Migration**:
|
||||
1. **Update guides** with new patterns discovered
|
||||
2. **Document coordinator fixes** that become common
|
||||
3. **Enhance agent instructions** based on recurring issues
|
||||
4. **Track success metrics** - automatic vs coordinator integration rates
|
||||
76
.claude/projects/js-to-ts/PROJECT.md
Normal file
76
.claude/projects/js-to-ts/PROJECT.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# JavaScript to TypeScript Migration Project
|
||||
|
||||
Progressive migration of 219 JS/JSX files to TypeScript in Apache Superset frontend.
|
||||
|
||||
## 📁 Project Documentation
|
||||
|
||||
- **[AGENT.md](./AGENT.md)** - Complete technical migration guide for agents (includes type reference, patterns, validation)
|
||||
- **[COORDINATOR.md](./COORDINATOR.md)** - Strategic workflow for coordinators (file selection, task management, integration)
|
||||
|
||||
## 🎯 Quick Start
|
||||
|
||||
**For Agents:** Read [AGENT.md](./AGENT.md) for complete migration instructions
|
||||
**For Coordinators:** Read [COORDINATOR.md](./COORDINATOR.md) for workflow and [AGENT.md](./AGENT.md) for supervision
|
||||
|
||||
**Command:** `/js-to-ts <filename>` - See [../../commands/js-to-ts.md](../../commands/js-to-ts.md)
|
||||
|
||||
## 📊 Migration Progress
|
||||
|
||||
**Scope**: 219 files total (112 JS + 107 JSX)
|
||||
- Production files: 139 (63%)
|
||||
- Test files: 80 (37%)
|
||||
|
||||
**Strategy**: Leaf-first migration with dependency-aware coordination
|
||||
|
||||
### Completed Migrations ✅
|
||||
|
||||
1. **roundDecimal** - `plugins/legacy-plugin-chart-map-box/src/utils/roundDecimal.js`
|
||||
- Migrated core + test files
|
||||
- Added proper TypeScript function signature with optional precision parameter
|
||||
- All tests pass
|
||||
|
||||
2. **timeGrainSqlaAnimationOverrides** - `src/explore/controlPanels/timeGrainSqlaAnimationOverrides.js`
|
||||
- Migrated to TypeScript with ControlPanelState and Dataset types
|
||||
- Added TimeGrainOverrideState interface for return type
|
||||
- Used type guards for safe property access
|
||||
|
||||
3. **DebouncedMessageQueue** - `src/utils/DebouncedMessageQueue.js`
|
||||
- Migrated to TypeScript with proper generics
|
||||
- Created DebouncedMessageQueueOptions interface
|
||||
- **CREATED test file** with 4 comprehensive test cases
|
||||
- Excellent class property typing with private/readonly modifiers
|
||||
|
||||
**Files Migrated**: 3/219 (1.4%)
|
||||
**Tests Created**: 2 (roundDecimal had existing, DebouncedMessageQueue created)
|
||||
|
||||
### Next Candidates (Leaf Nodes) 🎯
|
||||
|
||||
**Identified leaf files with no JS/JSX dependencies:**
|
||||
- `src/utils/hostNamesConfig.js` - Domain configuration utility
|
||||
- `src/explore/controlPanels/Separator.js` - Control panel configuration
|
||||
- `src/middleware/loggerMiddleware.js` - Logging middleware
|
||||
|
||||
**Migration Quality**: All completed migrations have:
|
||||
- ✅ Zero `any` types
|
||||
- ✅ Proper TypeScript compilation
|
||||
- ✅ ESLint validation passed
|
||||
- ✅ Test coverage (created where missing)
|
||||
|
||||
---
|
||||
|
||||
## 📈 Success Metrics
|
||||
|
||||
**Per-File Gates**:
|
||||
- ✅ `npm run type` passes after each migration
|
||||
- ✅ Zero `any` types introduced
|
||||
- ✅ All imports properly typed
|
||||
- ✅ Types filed in correct hierarchy
|
||||
|
||||
**Overall Progress**:
|
||||
- **Automatic Integration Rate**: 100% (3/3 migrations required no coordinator fixes)
|
||||
- **Test Coverage**: Improved (1 new test file created)
|
||||
- **Type Safety**: Enhanced with proper interfaces and generics
|
||||
|
||||
---
|
||||
|
||||
*This is a claudette-managed progressive refactor. All documentation and coordination resources are organized under `.claude/projects/js-to-ts/`*
|
||||
14
.github/CODEOWNERS
vendored
14
.github/CODEOWNERS
vendored
@@ -33,10 +33,10 @@
|
||||
|
||||
# Notify PMC members of changes to extension-related files
|
||||
|
||||
/superset-core/ @michael-s-molina @villebro
|
||||
/superset-extensions-cli/ @michael-s-molina @villebro
|
||||
/superset/core/ @michael-s-molina @villebro
|
||||
/superset/extensions/ @michael-s-molina @villebro
|
||||
/superset-frontend/src/packages/superset-core/ @michael-s-molina @villebro
|
||||
/superset-frontend/src/core/ @michael-s-molina @villebro
|
||||
/superset-frontend/src/extensions/ @michael-s-molina @villebro
|
||||
/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-extensions-cli/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-frontend/src/packages/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-frontend/src/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
/superset-frontend/src/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
|
||||
|
||||
2
.github/copilot-instructions.md
vendored
2
.github/copilot-instructions.md
vendored
@@ -1 +1 @@
|
||||
../LLMS.md
|
||||
../AGENTS.md
|
||||
70
.github/workflows/bashlib.sh
vendored
70
.github/workflows/bashlib.sh
vendored
@@ -182,6 +182,76 @@ cypress-run-all() {
|
||||
kill $flaskProcessId
|
||||
}
|
||||
|
||||
playwright-install() {
|
||||
cd "$GITHUB_WORKSPACE/superset-frontend"
|
||||
|
||||
say "::group::Install Playwright browsers"
|
||||
npx playwright install --with-deps chromium
|
||||
# Create output directories for test results and debugging
|
||||
mkdir -p playwright-results
|
||||
mkdir -p test-results
|
||||
say "::endgroup::"
|
||||
}
|
||||
|
||||
playwright-run() {
|
||||
local APP_ROOT=$1
|
||||
|
||||
# Start Flask from the project root (same as Cypress)
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
local flasklog="${HOME}/flask-playwright.log"
|
||||
local port=8081
|
||||
PLAYWRIGHT_BASE_URL="http://localhost:${port}"
|
||||
if [ -n "$APP_ROOT" ]; then
|
||||
export SUPERSET_APP_ROOT=$APP_ROOT
|
||||
PLAYWRIGHT_BASE_URL=${PLAYWRIGHT_BASE_URL}${APP_ROOT}/
|
||||
fi
|
||||
export PLAYWRIGHT_BASE_URL
|
||||
|
||||
nohup flask run --no-debugger -p $port >"$flasklog" 2>&1 </dev/null &
|
||||
local flaskProcessId=$!
|
||||
|
||||
# Ensure cleanup on exit
|
||||
trap "kill $flaskProcessId 2>/dev/null || true" EXIT
|
||||
|
||||
# Wait for server to be ready with health check
|
||||
local timeout=60
|
||||
say "Waiting for Flask server to start on port $port..."
|
||||
while [ $timeout -gt 0 ]; do
|
||||
if curl -f ${PLAYWRIGHT_BASE_URL}/health >/dev/null 2>&1; then
|
||||
say "Flask server is ready"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
timeout=$((timeout - 1))
|
||||
done
|
||||
|
||||
if [ $timeout -eq 0 ]; then
|
||||
echo "::error::Flask server failed to start within 60 seconds"
|
||||
echo "::group::Flask startup log"
|
||||
cat "$flasklog"
|
||||
echo "::endgroup::"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Change to frontend directory for Playwright execution
|
||||
cd "$GITHUB_WORKSPACE/superset-frontend"
|
||||
|
||||
say "::group::Run Playwright tests"
|
||||
echo "Running Playwright with baseURL: ${PLAYWRIGHT_BASE_URL}"
|
||||
npx playwright test auth/login --reporter=github --output=playwright-results
|
||||
local status=$?
|
||||
say "::endgroup::"
|
||||
|
||||
# After job is done, print out Flask log for debugging
|
||||
echo "::group::Flask log for Playwright run"
|
||||
cat "$flasklog"
|
||||
echo "::endgroup::"
|
||||
# make sure the program exits
|
||||
kill $flaskProcessId
|
||||
|
||||
return $status
|
||||
}
|
||||
|
||||
eyes-storybook-dependencies() {
|
||||
say "::group::install eyes-storyook dependencies"
|
||||
sudo apt-get update -y && sudo apt-get -y install gconf-service ca-certificates libxshmfence-dev fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libglib2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libnspr4 libnss3 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release xdg-utils libappindicator1
|
||||
|
||||
22
.github/workflows/showtime-trigger.yml
vendored
22
.github/workflows/showtime-trigger.yml
vendored
@@ -61,17 +61,8 @@ jobs:
|
||||
console.log(`📊 Permission level for ${actor}: ${permission.permission}`);
|
||||
const authorized = ['write', 'admin'].includes(permission.permission);
|
||||
|
||||
if (!authorized) {
|
||||
console.log(`🚨 Unauthorized user ${actor} - skipping all operations`);
|
||||
core.setOutput('authorized', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Authorized maintainer: ${actor}`);
|
||||
core.setOutput('authorized', 'true');
|
||||
|
||||
// If this is a synchronize event, check if Showtime is active and set blocked label
|
||||
if (context.eventName === 'pull_request_target' && context.payload.action === 'synchronize') {
|
||||
// If this is a synchronize event from unauthorized user, check if Showtime is active and set blocked label
|
||||
if (!authorized && context.eventName === 'pull_request_target' && context.payload.action === 'synchronize') {
|
||||
console.log(`🔒 Synchronize event detected - checking if Showtime is active`);
|
||||
|
||||
// Check if PR has any circus tent labels (Showtime is in use)
|
||||
@@ -99,6 +90,15 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
if (!authorized) {
|
||||
console.log(`🚨 Unauthorized user ${actor} - skipping all operations`);
|
||||
core.setOutput('authorized', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Authorized maintainer: ${actor}`);
|
||||
core.setOutput('authorized', 'true');
|
||||
|
||||
- name: Install Superset Showtime
|
||||
if: steps.auth.outputs.authorized == 'true'
|
||||
run: |
|
||||
|
||||
2
.github/workflows/superset-frontend.yml
vendored
2
.github/workflows/superset-frontend.yml
vendored
@@ -143,7 +143,7 @@ jobs:
|
||||
- name: tsc
|
||||
run: |
|
||||
docker run --rm $TAG bash -c \
|
||||
"npm run type"
|
||||
"npm run plugins:build && npm run type"
|
||||
|
||||
validate-frontend:
|
||||
needs: frontend-build
|
||||
|
||||
141
.github/workflows/superset-playwright.yml
vendored
Normal file
141
.github/workflows/superset-playwright.yml
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
name: Playwright E2E Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "[0-9].[0-9]*"
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'The branch or tag to checkout'
|
||||
required: false
|
||||
default: ''
|
||||
pr_id:
|
||||
description: 'The pull request ID to checkout'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
playwright-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
# Allow workflow to succeed even if tests fail during shadow mode
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
browser: ["chromium"]
|
||||
app_root: ["", "/app/prefix"]
|
||||
env:
|
||||
SUPERSET_ENV: development
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
REDIS_PORT: 16379
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: superset
|
||||
POSTGRES_PASSWORD: superset
|
||||
ports:
|
||||
- 15432:5432
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 16379:6379
|
||||
steps:
|
||||
# -------------------------------------------------------
|
||||
# Conditional checkout based on context (same as Cypress workflow)
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
submodules: recursive
|
||||
# -------------------------------------------------------
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
- name: Setup postgres
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: setup-postgres
|
||||
- name: Import test data
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: npm-install
|
||||
- name: Build javascript packages
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: build-instrumented-assets
|
||||
- name: Install Playwright
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: playwright-install
|
||||
- name: Run Playwright
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
with:
|
||||
run: playwright-run ${{ matrix.app_root }}
|
||||
- name: Set safe app root
|
||||
if: failure()
|
||||
id: set-safe-app-root
|
||||
run: |
|
||||
APP_ROOT="${{ matrix.app_root }}"
|
||||
SAFE_APP_ROOT=${APP_ROOT//\//_}
|
||||
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
|
||||
- name: Upload Playwright Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/superset-frontend/playwright-results/
|
||||
${{ github.workspace }}/superset-frontend/test-results/
|
||||
name: playwright-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
|
||||
@@ -82,6 +82,7 @@ intro_header.txt
|
||||
|
||||
# for LLMs
|
||||
llm-context.md
|
||||
AGENTS.md
|
||||
LLMS.md
|
||||
CLAUDE.md
|
||||
CURSOR.md
|
||||
|
||||
@@ -15,8 +15,9 @@ Apache Superset is a data visualization platform with Flask/Python backend and R
|
||||
|
||||
### Testing Strategy Migration
|
||||
- **Prefer unit tests** over integration tests
|
||||
- **Prefer integration tests** over Cypress end-to-end tests
|
||||
- **Cypress is last resort** - Actively moving away from Cypress
|
||||
- **Prefer integration tests** over end-to-end tests
|
||||
- **Use Playwright for E2E tests** - Migrating from Cypress
|
||||
- **Cypress is deprecated** - Will be removed once migration is completed
|
||||
- **Use Jest + React Testing Library** for component testing
|
||||
- **Use `test()` instead of `describe()`** - Follow [avoid nesting when testing](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) principles
|
||||
|
||||
@@ -67,7 +68,11 @@ superset/
|
||||
|
||||
### Apache License Headers
|
||||
- **New files require ASF license headers** - When creating new code files, include the standard Apache Software Foundation license header
|
||||
- **LLM instruction files are excluded** - Files like LLMS.md, CLAUDE.md, etc. are in `.rat-excludes` to avoid header token overhead
|
||||
- **LLM instruction files are excluded** - Files like AGENTS.md, CLAUDE.md, etc. are in `.rat-excludes` to avoid header token overhead
|
||||
|
||||
### Code Comments
|
||||
- **Avoid time-specific language** - Don't use words like "now", "currently", "today" in code comments as they become outdated
|
||||
- **Write timeless comments** - Comments should remain accurate regardless of when they're read
|
||||
|
||||
## Documentation Requirements
|
||||
|
||||
@@ -97,6 +102,17 @@ superset/
|
||||
- **`selectOption()`** - Select component helper
|
||||
- **React Testing Library** - NO Enzyme (removed)
|
||||
|
||||
### Test Structure Guidelines
|
||||
- **Use `test()` instead of `describe()` and `it()`** - Follow the [avoid nesting when testing](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) principle
|
||||
- **Why**: Reduces unnecessary nesting, improves test isolation, and makes tests more readable
|
||||
- **Pattern**: Write flat test files with descriptive test names that fully describe what's being tested
|
||||
- **Example**: Instead of nested `describe('Component', () => { it('should render', ...) })`, use `test('Component renders correctly', ...)`
|
||||
- **Benefits**:
|
||||
- Each test stands alone with a clear, searchable name
|
||||
- Easier to run individual tests
|
||||
- Forces you to write more descriptive test names
|
||||
- Reduces cognitive overhead from nested context switching
|
||||
|
||||
### Test Database Patterns
|
||||
- **Mock patterns**: Use `MagicMock()` for config objects, avoid `AsyncMock` for synchronous code
|
||||
- **API tests**: Update expected columns when adding new model fields
|
||||
@@ -107,6 +123,18 @@ superset/
|
||||
npm run test # All tests
|
||||
npm run test -- filename.test.tsx # Single file
|
||||
|
||||
# E2E Tests (Playwright - NEW)
|
||||
npm run playwright:test # All Playwright tests
|
||||
npm run playwright:ui # Interactive UI mode
|
||||
npm run playwright:headed # See browser during tests
|
||||
npx playwright test tests/auth/login.spec.ts # Single file
|
||||
npm run playwright:debug tests/auth/login.spec.ts # Debug specific file
|
||||
|
||||
# E2E Tests (Cypress - DEPRECATED)
|
||||
cd superset-frontend/cypress-base
|
||||
npm run cypress-run-chrome # All Cypress tests (headless)
|
||||
npm run cypress-debug # Interactive Cypress UI
|
||||
|
||||
# Backend
|
||||
pytest # All tests
|
||||
pytest tests/unit_tests/specific_test.py # Single file
|
||||
@@ -136,6 +164,19 @@ curl -f http://localhost:8088/health || echo "❌ Setup required - see https://s
|
||||
- **Use negation operator**: `~Model.field` instead of `== False` to avoid ruff E712 errors
|
||||
- **Example**: `~Model.is_active` instead of `Model.is_active == False`
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
**When creating pull requests:**
|
||||
|
||||
1. **Read the current PR template**: Always check `.github/PULL_REQUEST_TEMPLATE.md` for the latest format
|
||||
2. **Use the template sections**: Include all sections from the template (SUMMARY, BEFORE/AFTER, TESTING INSTRUCTIONS, ADDITIONAL INFORMATION)
|
||||
3. **Follow PR title conventions**: Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
- Format: `type(scope): description`
|
||||
- Example: `fix(dashboard): load charts correctly`
|
||||
- Types: `fix`, `feat`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`
|
||||
|
||||
**Important**: Always reference the actual template file at `.github/PULL_REQUEST_TEMPLATE.md` instead of using cached content, as the template may be updated over time.
|
||||
|
||||
## Pre-commit Validation
|
||||
|
||||
**Use pre-commit hooks for quality validation:**
|
||||
@@ -23,6 +23,8 @@ This file documents any backwards-incompatible changes in Superset and
|
||||
assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
|
||||
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
|
||||
- [34536](https://github.com/apache/superset/pull/34536): The `ENVIRONMENT_TAG_CONFIG` color values have changed to support only Ant Design semantic colors. Update your `superset_config.py`:
|
||||
|
||||
@@ -28,6 +28,7 @@ x-superset-image: &superset-image apachesuperset.docker.scarf.sh/apache/superset
|
||||
x-superset-volumes:
|
||||
&superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
|
||||
- ./docker:/app/docker
|
||||
- ./superset-core:/app/superset-core
|
||||
- superset_home:/app/superset_home
|
||||
|
||||
services:
|
||||
|
||||
@@ -162,8 +162,11 @@ services:
|
||||
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
||||
# configuring the dev-server to use the host.docker.internal to connect to the backend
|
||||
superset: "http://superset-light:8088"
|
||||
# Webpack dev server configuration
|
||||
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-127.0.0.1}"
|
||||
WEBPACK_DEVSERVER_PORT: "${WEBPACK_DEVSERVER_PORT:-9000}"
|
||||
ports:
|
||||
- "127.0.0.1:${NODE_PORT:-9001}:9000" # Parameterized port
|
||||
- "${NODE_PORT:-9001}:9000" # Parameterized port, accessible on all interfaces
|
||||
command: ["/app/docker/docker-frontend.sh"]
|
||||
env_file:
|
||||
- path: docker/.env # default
|
||||
|
||||
@@ -29,9 +29,11 @@ x-superset-volumes: &superset-volumes
|
||||
# /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
|
||||
- ./docker:/app/docker
|
||||
- ./superset:/app/superset
|
||||
- ./superset-core:/app/superset-core
|
||||
- ./superset-frontend:/app/superset-frontend
|
||||
- superset_home:/app/superset_home
|
||||
- ./tests:/app/tests
|
||||
- superset_data:/app/data
|
||||
x-common-build: &common-build
|
||||
context: .
|
||||
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
|
||||
@@ -274,3 +276,5 @@ volumes:
|
||||
external: false
|
||||
redis:
|
||||
external: false
|
||||
superset_data:
|
||||
external: false
|
||||
|
||||
@@ -21,8 +21,15 @@ set -eo pipefail
|
||||
# Make python interactive
|
||||
if [ "$DEV_MODE" == "true" ]; then
|
||||
if [ "$(whoami)" = "root" ] && command -v uv > /dev/null 2>&1; then
|
||||
echo "Reinstalling the app in editable mode"
|
||||
uv pip install -e .
|
||||
# Always ensure superset-core is available
|
||||
echo "Installing superset-core in editable mode"
|
||||
uv pip install --no-deps -e /app/superset-core
|
||||
|
||||
# Only reinstall the main app for non-worker processes
|
||||
if [ "$1" != "worker" ] && [ "$1" != "beat" ]; then
|
||||
echo "Reinstalling the app in editable mode"
|
||||
uv pip install -e .
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
REQUIREMENTS_LOCAL="/app/docker/requirements-local.txt"
|
||||
@@ -34,7 +41,8 @@ if [ "$CYPRESS_CONFIG" == "true" ]; then
|
||||
export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset_cypress
|
||||
PORT=8081
|
||||
fi
|
||||
if [[ "$DATABASE_DIALECT" == postgres* ]] && [ "$(whoami)" = "root" ]; then
|
||||
# Skip postgres requirements installation for workers to avoid conflicts
|
||||
if [[ "$DATABASE_DIALECT" == postgres* ]] && [ "$(whoami)" = "root" ] && [ "$1" != "worker" ] && [ "$1" != "beat" ]; then
|
||||
# older images may not have the postgres dev requirements installed
|
||||
echo "Installing postgres requirements"
|
||||
if command -v uv > /dev/null 2>&1; then
|
||||
|
||||
@@ -138,7 +138,7 @@ try:
|
||||
from superset_config_docker import * # noqa: F403
|
||||
|
||||
logger.info(
|
||||
f"Loaded your Docker configuration at [{superset_config_docker.__file__}]"
|
||||
"Loaded your Docker configuration at [%s]", superset_config_docker.__file__
|
||||
)
|
||||
except ImportError:
|
||||
logger.info("Using default Docker config...")
|
||||
|
||||
@@ -36,11 +36,11 @@ Screenshots will be taken but no messages actually sent as long as `ALERT_REPORT
|
||||
#### In your `Dockerfile`
|
||||
|
||||
You'll need to extend the Superset image to include a headless browser. Your options include:
|
||||
- Use Playwright with Chrome: this is the recommended approach as of version >=4.1.x. A working example of a Dockerfile that installs these tools is provided under “Building your own production Docker image” on the [Docker Builds](/docs/installation/docker-builds#building-your-own-production-docker-image) page. Read the code comments there as you'll also need to change a feature flag in your config.
|
||||
- Use Playwright with Chrome: this is the recommended approach as of version 4.1.x or greater. A working example of a Dockerfile that installs these tools is provided under "Building your own production Docker image" on the [Docker Builds](/docs/installation/docker-builds#building-your-own-production-docker-image) page. Read the code comments there as you'll also need to change a feature flag in your config.
|
||||
- Use Firefox: you'll need to install geckodriver and Firefox.
|
||||
- Use Chrome without Playwright: you'll need to install Chrome and set the value of `WEBDRIVER_TYPE` to `"chrome"` in your `superset_config.py`.
|
||||
|
||||
In Superset versions <=4.0x, users installed Firefox or Chrome and that was documented here.
|
||||
In Superset versions prior to 4.1, users installed Firefox or Chrome and that was documented here.
|
||||
|
||||
Only the worker container needs the browser.
|
||||
|
||||
|
||||
@@ -363,110 +363,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
|
||||
]
|
||||
```
|
||||
|
||||
### Keycloak-Specific Configuration using Flask-OIDC
|
||||
|
||||
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
|
||||
|
||||
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
|
||||
|
||||
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
|
||||
|
||||
```python
|
||||
from flask_appbuilder.security.manager import AUTH_OID
|
||||
from superset.security import SupersetSecurityManager
|
||||
from flask_oidc import OpenIDConnect
|
||||
from flask_appbuilder.security.views import AuthOIDView
|
||||
from flask_login import login_user
|
||||
from urllib.parse import quote
|
||||
from flask_appbuilder.views import ModelView, SimpleFormView, expose
|
||||
from flask import (
|
||||
redirect,
|
||||
request
|
||||
)
|
||||
import logging
|
||||
|
||||
class OIDCSecurityManager(SupersetSecurityManager):
|
||||
|
||||
def __init__(self, appbuilder):
|
||||
super(OIDCSecurityManager, self).__init__(appbuilder)
|
||||
if self.auth_type == AUTH_OID:
|
||||
self.oid = OpenIDConnect(self.appbuilder.get_app)
|
||||
self.authoidview = AuthOIDCView
|
||||
|
||||
class AuthOIDCView(AuthOIDView):
|
||||
|
||||
@expose('/login/', methods=['GET', 'POST'])
|
||||
def login(self, flag=True):
|
||||
sm = self.appbuilder.sm
|
||||
oidc = sm.oid
|
||||
|
||||
@self.appbuilder.sm.oid.require_login
|
||||
def handle_login():
|
||||
user = sm.auth_user_oid(oidc.user_getfield('email'))
|
||||
|
||||
if user is None:
|
||||
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
|
||||
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
|
||||
info.get('email'), sm.find_role('Gamma'))
|
||||
|
||||
login_user(user, remember=False)
|
||||
return redirect(self.appbuilder.get_url_for_index)
|
||||
|
||||
return handle_login()
|
||||
|
||||
@expose('/logout/', methods=['GET', 'POST'])
|
||||
def logout(self):
|
||||
oidc = self.appbuilder.sm.oid
|
||||
|
||||
oidc.logout()
|
||||
super(AuthOIDCView, self).logout()
|
||||
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
|
||||
|
||||
return redirect(
|
||||
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
|
||||
```
|
||||
|
||||
Then add to your `superset_config.py` file:
|
||||
|
||||
```python
|
||||
from keycloak_security_manager import OIDCSecurityManager
|
||||
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
|
||||
import os
|
||||
|
||||
AUTH_TYPE = AUTH_OID
|
||||
SECRET_KEY: 'SomethingNotEntirelySecret'
|
||||
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
|
||||
OIDC_ID_TOKEN_COOKIE_SECURE = False
|
||||
OIDC_OPENID_REALM: '<myRealm>'
|
||||
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
|
||||
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
|
||||
|
||||
# Will allow user self registration, allowing to create Flask users from Authorized User
|
||||
AUTH_USER_REGISTRATION = True
|
||||
|
||||
# The default user self registration role
|
||||
AUTH_USER_REGISTRATION_ROLE = 'Public'
|
||||
```
|
||||
|
||||
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
|
||||
|
||||
```json
|
||||
{
|
||||
"<myOpenIDProvider>": {
|
||||
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
|
||||
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
|
||||
"client_id": "https://<myKeycloakDomain>",
|
||||
"client_secret": "<myClientSecret>",
|
||||
"redirect_uris": [
|
||||
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
|
||||
],
|
||||
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
|
||||
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
|
||||
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## LDAP Authentication
|
||||
|
||||
FAB supports authenticating user credentials against an LDAP server.
|
||||
|
||||
@@ -67,7 +67,7 @@ are compatible with Superset.
|
||||
| [IBM Netezza Performance Server](/docs/configuration/databases#ibm-netezza-performance-server) | `pip install nzalchemy` | `netezza+nzpy://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [MySQL](/docs/configuration/databases#mysql) | `pip install mysqlclient` | `mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [OceanBase](/docs/configuration/databases#oceanbase) | `pip install oceanbase_py` | `oceanbase://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [Oracle](/docs/configuration/databases#oracle) | `pip install cx_Oracle` | `oracle://<username>:<password>@<hostname>:<port>` |
|
||||
| [Oracle](/docs/configuration/databases#oracle) | `pip install oracledb` | `oracle://<username>:<password>@<hostname>:<port>` |
|
||||
| [Parseable](/docs/configuration/databases#parseable) | `pip install sqlalchemy-parseable` | `parseable://<UserName>:<DBPassword>@<Database Host>/<Stream Name>` |
|
||||
| [PostgreSQL](/docs/configuration/databases#postgres) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [Presto](/docs/configuration/databases#presto) | `pip install pyhive` | `presto://{username}:{password}@{hostname}:{port}/{database}` |
|
||||
|
||||
@@ -10,8 +10,15 @@ version: 1
|
||||
## Jinja Templates
|
||||
|
||||
SQL Lab and Explore supports [Jinja templating](https://jinja.palletsprojects.com/en/2.11.x/) in queries.
|
||||
To enable templating, the `ENABLE_TEMPLATE_PROCESSING` [feature flag](/docs/configuration/configuring-superset#feature-flags) needs to be enabled in
|
||||
`superset_config.py`. When templating is enabled, python code can be embedded in virtual datasets and
|
||||
To enable templating, the `ENABLE_TEMPLATE_PROCESSING` [feature flag](/docs/configuration/configuring-superset#feature-flags) needs to be enabled in `superset_config.py`.
|
||||
|
||||
> #### ⚠️ Security Warning
|
||||
>
|
||||
> While powerful, this feature executes template code on the server. Within the Superset security model, this is **intended functionality**, as users with permissions to edit charts and virtual datasets are considered **trusted users**.
|
||||
>
|
||||
> If you grant these permissions to untrusted users, this feature can be exploited as a **Server-Side Template Injection (SSTI)** vulnerability. Do not enable `ENABLE_TEMPLATE_PROCESSING` unless you fully understand and accept the associated security risks.
|
||||
|
||||
When templating is enabled, python code can be embedded in virtual datasets and
|
||||
in Custom SQL in the filter and metric controls in Explore. By default, the following variables are
|
||||
made available in the Jinja context:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ version: 1
|
||||
# Theming Superset
|
||||
|
||||
:::note
|
||||
apache-superset>=6.0
|
||||
`apache-superset>=6.0`
|
||||
:::
|
||||
|
||||
Superset now rides on **Ant Design v5's token-based theming**.
|
||||
@@ -165,6 +165,206 @@ Or in the CRUD interface theme JSON:
|
||||
|
||||
This feature works with the stock Docker image - no custom build required!
|
||||
|
||||
## ECharts Configuration Overrides
|
||||
|
||||
:::note
|
||||
Available since Superset 6.0
|
||||
:::
|
||||
|
||||
Superset provides fine-grained control over ECharts visualizations through theme-level configuration overrides. This allows you to customize the appearance and behavior of all ECharts-based charts without modifying individual chart configurations.
|
||||
|
||||
### Global ECharts Overrides
|
||||
|
||||
Apply settings to all ECharts visualizations using `echartsOptionsOverrides`:
|
||||
|
||||
```python
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3",
|
||||
# ... other Ant Design tokens
|
||||
},
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "10%",
|
||||
"right": "10%",
|
||||
"top": "15%",
|
||||
"bottom": "15%"
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)",
|
||||
"borderColor": "#ccc",
|
||||
"textStyle": {
|
||||
"color": "#fff"
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"fontSize": 14,
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Chart-Specific Overrides
|
||||
|
||||
Target specific chart types using `echartsOptionsOverridesByChartType`:
|
||||
|
||||
```python
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3",
|
||||
# ... other tokens
|
||||
},
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"echarts_pie": {
|
||||
"legend": {
|
||||
"orient": "vertical",
|
||||
"right": 10,
|
||||
"top": "center"
|
||||
}
|
||||
},
|
||||
"echarts_timeseries": {
|
||||
"xAxis": {
|
||||
"axisLabel": {
|
||||
"rotate": 45,
|
||||
"fontSize": 12
|
||||
}
|
||||
},
|
||||
"dataZoom": [{
|
||||
"type": "slider",
|
||||
"show": True,
|
||||
"start": 0,
|
||||
"end": 100
|
||||
}]
|
||||
},
|
||||
"echarts_bubble": {
|
||||
"grid": {
|
||||
"left": "15%",
|
||||
"bottom": "20%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### UI Configuration
|
||||
|
||||
You can also configure ECharts overrides through the theme CRUD interface:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3"
|
||||
},
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "10%",
|
||||
"right": "10%"
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)"
|
||||
}
|
||||
},
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"echarts_pie": {
|
||||
"legend": {
|
||||
"orient": "vertical",
|
||||
"right": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Override Precedence
|
||||
|
||||
The system applies overrides in the following order (last wins):
|
||||
|
||||
1. **Base ECharts theme** - Default Superset styling
|
||||
2. **Plugin options** - Chart-specific configurations
|
||||
3. **Global overrides** - `echartsOptionsOverrides`
|
||||
4. **Chart-specific overrides** - `echartsOptionsOverridesByChartType[chartType]`
|
||||
|
||||
This ensures chart-specific overrides take precedence over global ones.
|
||||
|
||||
### Common Chart Types
|
||||
|
||||
Available chart types for `echartsOptionsOverridesByChartType`:
|
||||
|
||||
- `echarts_timeseries` - Time series/line charts
|
||||
- `echarts_pie` - Pie and donut charts
|
||||
- `echarts_bubble` - Bubble/scatter charts
|
||||
- `echarts_funnel` - Funnel charts
|
||||
- `echarts_gauge` - Gauge charts
|
||||
- `echarts_radar` - Radar charts
|
||||
- `echarts_boxplot` - Box plot charts
|
||||
- `echarts_treemap` - Treemap charts
|
||||
- `echarts_sunburst` - Sunburst charts
|
||||
- `echarts_graph` - Network/graph charts
|
||||
- `echarts_sankey` - Sankey diagrams
|
||||
- `echarts_heatmap` - Heatmaps
|
||||
- `echarts_mixed_timeseries` - Mixed time series
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Start with global overrides** for consistent styling across all charts
|
||||
2. **Use chart-specific overrides** for unique requirements per visualization type
|
||||
3. **Test thoroughly** as overrides use deep merge - nested objects are combined, but arrays are completely replaced
|
||||
4. **Document your overrides** to help team members understand custom styling
|
||||
5. **Consider performance** - complex overrides may impact chart rendering speed
|
||||
|
||||
### Example: Corporate Branding
|
||||
|
||||
```python
|
||||
# Complete corporate theme with ECharts customization
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"colorPrimary": "#1B4D3E",
|
||||
"fontFamily": "Corporate Sans, Arial, sans-serif"
|
||||
},
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "8%",
|
||||
"right": "8%",
|
||||
"top": "12%",
|
||||
"bottom": "12%"
|
||||
},
|
||||
"textStyle": {
|
||||
"fontFamily": "Corporate Sans, Arial, sans-serif"
|
||||
},
|
||||
"title": {
|
||||
"textStyle": {
|
||||
"color": "#1B4D3E",
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"echarts_timeseries": {
|
||||
"xAxis": {
|
||||
"axisLabel": {
|
||||
"color": "#666",
|
||||
"fontSize": 11
|
||||
}
|
||||
}
|
||||
},
|
||||
"echarts_pie": {
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"fontSize": 12
|
||||
},
|
||||
"itemGap": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This feature provides powerful theming capabilities while maintaining the flexibility of ECharts' extensive configuration options.
|
||||
|
||||
## Advanced Features
|
||||
|
||||
- **System Themes**: Manage system-wide default and dark themes via UI or configuration
|
||||
|
||||
@@ -620,10 +620,10 @@ See [how tos](/docs/contributing/howtos#linting)
|
||||
|
||||
:::tip
|
||||
`act` compatibility of Superset's GHAs is not fully tested. Running `act` locally may or may not
|
||||
work for different actions, and may require fine tunning and local secret-handling.
|
||||
work for different actions, and may require fine tuning and local secret-handling.
|
||||
For those more intricate GHAs that are tricky to run locally, we recommend iterating
|
||||
directly on GHA's infrastructure, by pushing directly on a branch and monitoring GHA logs.
|
||||
For more targetted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
|
||||
For more targeted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
|
||||
:::
|
||||
|
||||
For automation and CI/CD, Superset makes extensive use of GitHub Actions (GHA). You
|
||||
@@ -631,7 +631,7 @@ can find all of the workflows and other assets under the `.github/` folder. This
|
||||
|
||||
- running the backend unit test suites (`tests/`)
|
||||
- running the frontend test suites (`superset-frontend/src/**.*.test.*`)
|
||||
- running our Cypress end-to-end tests (`superset-frontend/cypress-base/`)
|
||||
- running our Playwright end-to-end tests (`superset-frontend/playwright/`) and legacy Cypress tests (`superset-frontend/cypress-base/`)
|
||||
- linting the codebase, including all Python, Typescript and Javascript, yaml and beyond
|
||||
- checking for all sorts of other rules conventions
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ Committers may also update title to reflect the issue/PR content if the author-p
|
||||
|
||||
If the PR passes CI tests and does not have any `need:` labels, it is ready for review, add label `review` and/or `design-review`.
|
||||
|
||||
If an issue/PR has been inactive for >=30 days, it will be closed. If it does not have any status label, add `inactive`.
|
||||
If an issue/PR has been inactive for at least 30 days, it will be closed. If it does not have any status label, add `inactive`.
|
||||
|
||||
When creating a PR, if you're aiming to have it included in a specific release, please tag it with the version label. For example, to have a PR considered for inclusion in Superset 1.1 use the label `v1.1`.
|
||||
|
||||
|
||||
@@ -225,21 +225,57 @@ npm run test -- path/to/file.js
|
||||
|
||||
### E2E Integration Testing
|
||||
|
||||
For E2E testing, we recommend that you use a `docker compose` backend
|
||||
**Note: We are migrating from Cypress to Playwright. Use Playwright for new tests.**
|
||||
|
||||
#### Playwright (Recommended - NEW)
|
||||
|
||||
For E2E testing with Playwright, use the same `docker compose` backend:
|
||||
|
||||
```bash
|
||||
CYPRESS_CONFIG=true docker compose up --build
|
||||
```
|
||||
`docker compose` will get to work and expose a Cypress-ready Superset app.
|
||||
This app uses a different database schema (`superset_cypress`) to keep it isolated from
|
||||
your other dev environmen(s)t, a specific set of examples, and a set of configurations that
|
||||
aligns with the expectations within the end-to-end tests. Also note that it's served on a
|
||||
different port than the default port for the backend (`8088`).
|
||||
|
||||
Now in another terminal, let's get ready to execute some Cypress commands. First, tell cypress
|
||||
to connect to the Cypress-ready Superset backend.
|
||||
The backend setup is identical - this exposes a test-ready Superset app on port 8081 with isolated database schema (`superset_cypress`), test data, and configurations.
|
||||
|
||||
Now in another terminal, run Playwright tests:
|
||||
|
||||
```bash
|
||||
# Navigate to frontend directory (Playwright config is here)
|
||||
cd superset-frontend
|
||||
|
||||
# Run all Playwright tests
|
||||
npm run playwright:test
|
||||
# or: npx playwright test
|
||||
|
||||
# Run with interactive UI for debugging
|
||||
npm run playwright:ui
|
||||
# or: npx playwright test --ui
|
||||
|
||||
# Run in headed mode (see browser)
|
||||
npm run playwright:headed
|
||||
# or: npx playwright test --headed
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test tests/auth/login.spec.ts
|
||||
|
||||
# Run with debug mode (step through tests)
|
||||
npm run playwright:debug tests/auth/login.spec.ts
|
||||
# or: npx playwright test --debug tests/auth/login.spec.ts
|
||||
|
||||
# Generate test report
|
||||
npx playwright show-report
|
||||
```
|
||||
|
||||
Configuration is in `superset-frontend/playwright.config.ts`. Base URL is automatically set to `http://localhost:8088` but will use `PLAYWRIGHT_BASE_URL` if provided.
|
||||
|
||||
#### Cypress (DEPRECATED - will be removed in Phase 5)
|
||||
|
||||
:::warning
|
||||
Cypress is being phased out in favor of Playwright. Use Playwright for all new tests.
|
||||
:::
|
||||
|
||||
```bash
|
||||
# Set base URL for Cypress
|
||||
CYPRESS_BASE_URL=http://localhost:8081
|
||||
```
|
||||
|
||||
@@ -627,7 +663,7 @@ feature flag to `true`, you can add the following line to the PR body/descriptio
|
||||
FEATURE_TAGGING_SYSTEM=true
|
||||
```
|
||||
|
||||
Simarly, it's possible to disable feature flags with:
|
||||
Similarly, it's possible to disable feature flags with:
|
||||
|
||||
```
|
||||
FEATURE_TAGGING_SYSTEM=false
|
||||
|
||||
@@ -282,5 +282,5 @@ address.
|
||||
When running `docker compose up`, docker will build what is required behind the scene, but
|
||||
may use the docker cache if assets already exist. Running `docker compose build` prior to
|
||||
`docker compose up` or the equivalent shortcut `docker compose up --build` ensures that your
|
||||
docker images matche the definition in the repository. This should only apply to the main
|
||||
docker images match the definition in the repository. This should only apply to the main
|
||||
docker-compose.yml file (default) and not to the alternative methods defined above.
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"storybook": "^8.6.11",
|
||||
"swagger-ui-react": "^5.27.1",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"ts-loader": "^9.5.2"
|
||||
"ts-loader": "^9.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.8.1",
|
||||
@@ -73,7 +73,7 @@
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||
"@typescript-eslint/parser": "^8.37.0",
|
||||
"@typescript-eslint/parser": "^8.42.0",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.3",
|
||||
|
||||
@@ -363,110 +363,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
|
||||
]
|
||||
```
|
||||
|
||||
### Keycloak-Specific Configuration using Flask-OIDC
|
||||
|
||||
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
|
||||
|
||||
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
|
||||
|
||||
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
|
||||
|
||||
```python
|
||||
from flask_appbuilder.security.manager import AUTH_OID
|
||||
from superset.security import SupersetSecurityManager
|
||||
from flask_oidc import OpenIDConnect
|
||||
from flask_appbuilder.security.views import AuthOIDView
|
||||
from flask_login import login_user
|
||||
from urllib.parse import quote
|
||||
from flask_appbuilder.views import ModelView, SimpleFormView, expose
|
||||
from flask import (
|
||||
redirect,
|
||||
request
|
||||
)
|
||||
import logging
|
||||
|
||||
class OIDCSecurityManager(SupersetSecurityManager):
|
||||
|
||||
def __init__(self, appbuilder):
|
||||
super(OIDCSecurityManager, self).__init__(appbuilder)
|
||||
if self.auth_type == AUTH_OID:
|
||||
self.oid = OpenIDConnect(self.appbuilder.get_app)
|
||||
self.authoidview = AuthOIDCView
|
||||
|
||||
class AuthOIDCView(AuthOIDView):
|
||||
|
||||
@expose('/login/', methods=['GET', 'POST'])
|
||||
def login(self, flag=True):
|
||||
sm = self.appbuilder.sm
|
||||
oidc = sm.oid
|
||||
|
||||
@self.appbuilder.sm.oid.require_login
|
||||
def handle_login():
|
||||
user = sm.auth_user_oid(oidc.user_getfield('email'))
|
||||
|
||||
if user is None:
|
||||
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
|
||||
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
|
||||
info.get('email'), sm.find_role('Gamma'))
|
||||
|
||||
login_user(user, remember=False)
|
||||
return redirect(self.appbuilder.get_url_for_index)
|
||||
|
||||
return handle_login()
|
||||
|
||||
@expose('/logout/', methods=['GET', 'POST'])
|
||||
def logout(self):
|
||||
oidc = self.appbuilder.sm.oid
|
||||
|
||||
oidc.logout()
|
||||
super(AuthOIDCView, self).logout()
|
||||
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
|
||||
|
||||
return redirect(
|
||||
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
|
||||
```
|
||||
|
||||
Then add to your `superset_config.py` file:
|
||||
|
||||
```python
|
||||
from keycloak_security_manager import OIDCSecurityManager
|
||||
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
|
||||
import os
|
||||
|
||||
AUTH_TYPE = AUTH_OID
|
||||
SECRET_KEY: 'SomethingNotEntirelySecret'
|
||||
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
|
||||
OIDC_ID_TOKEN_COOKIE_SECURE = False
|
||||
OIDC_OPENID_REALM: '<myRealm>'
|
||||
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
|
||||
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
|
||||
|
||||
# Will allow user self registration, allowing to create Flask users from Authorized User
|
||||
AUTH_USER_REGISTRATION = True
|
||||
|
||||
# The default user self registration role
|
||||
AUTH_USER_REGISTRATION_ROLE = 'Public'
|
||||
```
|
||||
|
||||
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
|
||||
|
||||
```json
|
||||
{
|
||||
"<myOpenIDProvider>": {
|
||||
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
|
||||
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
|
||||
"client_id": "https://<myKeycloakDomain>",
|
||||
"client_secret": "<myClientSecret>",
|
||||
"redirect_uris": [
|
||||
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
|
||||
],
|
||||
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
|
||||
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
|
||||
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## LDAP Authentication
|
||||
|
||||
FAB supports authenticating user credentials against an LDAP server.
|
||||
|
||||
@@ -620,10 +620,10 @@ See [how tos](/docs/contributing/howtos#linting)
|
||||
|
||||
:::tip
|
||||
`act` compatibility of Superset's GHAs is not fully tested. Running `act` locally may or may not
|
||||
work for different actions, and may require fine tunning and local secret-handling.
|
||||
work for different actions, and may require fine tuning and local secret-handling.
|
||||
For those more intricate GHAs that are tricky to run locally, we recommend iterating
|
||||
directly on GHA's infrastructure, by pushing directly on a branch and monitoring GHA logs.
|
||||
For more targetted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
|
||||
For more targeted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
|
||||
:::
|
||||
|
||||
For automation and CI/CD, Superset makes extensive use of GitHub Actions (GHA). You
|
||||
|
||||
@@ -232,7 +232,7 @@ CYPRESS_CONFIG=true docker compose up --build
|
||||
```
|
||||
`docker compose` will get to work and expose a Cypress-ready Superset app.
|
||||
This app uses a different database schema (`superset_cypress`) to keep it isolated from
|
||||
your other dev environmen(s)t, a specific set of examples, and a set of configurations that
|
||||
your other dev environment(s), a specific set of examples, and a set of configurations that
|
||||
aligns with the expectations within the end-to-end tests. Also note that it's served on a
|
||||
different port than the default port for the backend (`8088`).
|
||||
|
||||
@@ -627,7 +627,7 @@ feature flag to `true`, you can add the following line to the PR body/descriptio
|
||||
FEATURE_TAGGING_SYSTEM=true
|
||||
```
|
||||
|
||||
Simarly, it's possible to disable feature flags with:
|
||||
Similarly, it's possible to disable feature flags with:
|
||||
|
||||
```
|
||||
FEATURE_TAGGING_SYSTEM=false
|
||||
|
||||
@@ -282,5 +282,5 @@ address.
|
||||
When running `docker compose up`, docker will build what is required behind the scene, but
|
||||
may use the docker cache if assets already exist. Running `docker compose build` prior to
|
||||
`docker compose up` or the equivalent shortcut `docker compose up --build` ensures that your
|
||||
docker images matche the definition in the repository. This should only apply to the main
|
||||
docker images match the definition in the repository. This should only apply to the main
|
||||
docker-compose.yml file (default) and not to the alternative methods defined above.
|
||||
|
||||
@@ -4102,7 +4102,7 @@
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/parser@8.40.0", "@typescript-eslint/parser@^8.37.0":
|
||||
"@typescript-eslint/parser@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.40.0.tgz#1bc9f3701ced29540eb76ff2d95ce0d52ddc7e69"
|
||||
integrity sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==
|
||||
@@ -4113,6 +4113,17 @@
|
||||
"@typescript-eslint/visitor-keys" "8.40.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/parser@^8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.42.0.tgz#20ea66f4867981fb5bb62cbe1454250fc4a440ab"
|
||||
integrity sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "8.42.0"
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/typescript-estree" "8.42.0"
|
||||
"@typescript-eslint/visitor-keys" "8.42.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/project-service@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.40.0.tgz#1b7ba6079ff580c3215882fe75a43e5d3ed166b9"
|
||||
@@ -4122,6 +4133,15 @@
|
||||
"@typescript-eslint/types" "^8.40.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/project-service@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.42.0.tgz#636eb3418b6c42c98554dce884943708bf41a583"
|
||||
integrity sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils" "^8.42.0"
|
||||
"@typescript-eslint/types" "^8.42.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz#2fbfcc8643340d8cd692267e61548b946190be8a"
|
||||
@@ -4130,11 +4150,24 @@
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
"@typescript-eslint/visitor-keys" "8.40.0"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz#36016757bc85b46ea42bae47b61f9421eddedde3"
|
||||
integrity sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/visitor-keys" "8.42.0"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.40.0", "@typescript-eslint/tsconfig-utils@^8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz#8e8fdb9b988854aedd04abdde3239c4bdd2d26e4"
|
||||
integrity sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.42.0", "@typescript-eslint/tsconfig-utils@^8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz#21a3e74396fd7443ff930bc41b27789ba7e9236e"
|
||||
integrity sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==
|
||||
|
||||
"@typescript-eslint/type-utils@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz#a7e4a1f0815dd0ba3e4eef945cc87193ca32c422"
|
||||
@@ -4146,11 +4179,16 @@
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/types@8.40.0", "@typescript-eslint/types@^8.40.0":
|
||||
"@typescript-eslint/types@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.40.0.tgz#0b580fdf643737aa5c01285314b5c6e9543846a9"
|
||||
integrity sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==
|
||||
|
||||
"@typescript-eslint/types@8.42.0", "@typescript-eslint/types@^8.40.0", "@typescript-eslint/types@^8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.42.0.tgz#ae15c09cebda20473772902033328e87372db008"
|
||||
integrity sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz#295149440ce7da81c790a4e14e327599a3a1e5c9"
|
||||
@@ -4167,6 +4205,22 @@
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz#593c3af87d4462252c0d7239d1720b84a1b56864"
|
||||
integrity sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service" "8.42.0"
|
||||
"@typescript-eslint/tsconfig-utils" "8.42.0"
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/visitor-keys" "8.42.0"
|
||||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
is-glob "^4.0.3"
|
||||
minimatch "^9.0.4"
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/utils@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.40.0.tgz#8d0c6430ed2f5dc350784bb0d8be514da1e54054"
|
||||
@@ -4185,6 +4239,14 @@
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
|
||||
"@typescript-eslint/visitor-keys@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz#87c6caaa1ac307bc73a87c1fc469f88f0162f27e"
|
||||
integrity sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
|
||||
"@ungap/structured-clone@^1.0.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
|
||||
@@ -4704,9 +4766,9 @@ available-typed-arrays@^1.0.7:
|
||||
possible-typed-array-names "^1.0.0"
|
||||
|
||||
axios@^1.9.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.11.0.tgz#c2ec219e35e414c025b2095e8b8280278478fdb6"
|
||||
integrity sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.0.tgz#11248459be05a5ee493485628fa0e4323d0abfc3"
|
||||
integrity sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.4"
|
||||
@@ -13139,10 +13201,10 @@ ts-dedent@^2.0.0, ts-dedent@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
|
||||
integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
|
||||
|
||||
ts-loader@^9.5.2:
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.2.tgz#1f3d7f4bb709b487aaa260e8f19b301635d08020"
|
||||
integrity sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==
|
||||
ts-loader@^9.5.4:
|
||||
version "9.5.4"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.4.tgz#44b571165c10fb5a90744aa5b7e119233c4f4585"
|
||||
integrity sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==
|
||||
dependencies:
|
||||
chalk "^4.1.0"
|
||||
enhanced-resolve "^5.0.0"
|
||||
|
||||
@@ -29,7 +29,7 @@ maintainers:
|
||||
- name: craig-rueda
|
||||
email: craig@craigrueda.com
|
||||
url: https://github.com/craig-rueda
|
||||
version: 0.15.0 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
|
||||
version: 0.15.1 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: 13.4.4
|
||||
|
||||
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
|
||||
|
||||
# superset
|
||||
|
||||

|
||||

|
||||
|
||||
Apache Superset is a modern, enterprise-ready business intelligence web application
|
||||
|
||||
@@ -203,6 +203,7 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
|
||||
| supersetNode.connections.db_name | string | `"superset"` | |
|
||||
| supersetNode.connections.db_pass | string | `"superset"` | |
|
||||
| supersetNode.connections.db_port | string | `"5432"` | |
|
||||
| supersetNode.connections.db_type | string | `"postgresql"` | Database type for Superset metadata (Supported types: "postgresql", "mysql") |
|
||||
| supersetNode.connections.db_user | string | `"superset"` | |
|
||||
| supersetNode.connections.redis_cache_db | string | `"1"` | |
|
||||
| supersetNode.connections.redis_celery_db | string | `"0"` | |
|
||||
|
||||
@@ -96,7 +96,18 @@ CACHE_CONFIG = {
|
||||
}
|
||||
DATA_CACHE_CONFIG = CACHE_CONFIG
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{env('DB_USER')}:{env('DB_PASS')}@{env('DB_HOST')}:{env('DB_PORT')}/{env('DB_NAME')}"
|
||||
|
||||
if os.getenv("SQLALCHEMY_DATABASE_URI"):
|
||||
SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI")
|
||||
else:
|
||||
{{- if eq .Values.supersetNode.connections.db_type "postgresql" }}
|
||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
|
||||
{{- else if eq .Values.supersetNode.connections.db_type "mysql" }}
|
||||
SQLALCHEMY_DATABASE_URI = f"mysql+mysqldb://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
|
||||
{{- else }}
|
||||
{{ fail (printf "Unsupported database type: %s. Please use 'postgresql' or 'mysql'." .Values.supersetNode.connections.db_type) }}
|
||||
{{- end }}
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = True
|
||||
|
||||
class CeleryConfig:
|
||||
|
||||
@@ -289,6 +289,8 @@ supersetNode:
|
||||
enabled: false
|
||||
ssl_cert_reqs: CERT_NONE
|
||||
# You need to change below configuration incase bringing own PostgresSQL instance and also set postgresql.enabled:false
|
||||
# -- Database type for Superset metadata (Supported types: "postgresql", "mysql")
|
||||
db_type: "postgresql"
|
||||
db_host: "{{ .Release.Name }}-postgresql"
|
||||
db_port: "5432"
|
||||
db_user: superset
|
||||
|
||||
@@ -48,7 +48,7 @@ dependencies = [
|
||||
"cryptography>=42.0.4, <45.0.0",
|
||||
"deprecation>=2.1.0, <2.2.0",
|
||||
"flask>=2.2.5, <3.0.0",
|
||||
"flask-appbuilder>=4.8.1, <5.0.0",
|
||||
"flask-appbuilder>=5.0.0,<6",
|
||||
"flask-caching>=2.1.0, <3",
|
||||
"flask-compress>=1.13, <2.0",
|
||||
"flask-talisman>=1.0.0, <2.0",
|
||||
@@ -76,7 +76,7 @@ dependencies = [
|
||||
"packaging",
|
||||
# --------------------------
|
||||
# pandas and related (wanting pandas[performance] without numba as it's 100+MB and not needed)
|
||||
"pandas[excel]>=2.0.3, <2.1",
|
||||
"pandas[excel]>=2.0.3, <2.2",
|
||||
"bottleneck", # recommended performance dependency for pandas, see https://pandas.pydata.org/docs/getting_started/install.html#performance-dependencies-recommended
|
||||
# --------------------------
|
||||
"parsedatetime",
|
||||
@@ -84,11 +84,12 @@ dependencies = [
|
||||
"pgsanity",
|
||||
"Pillow>=11.0.0, <12",
|
||||
"polyline>=2.0.0, <3.0",
|
||||
"pydantic>=2.8.0",
|
||||
"pyparsing>=3.0.6, <4",
|
||||
"python-dateutil",
|
||||
"python-dotenv", # optional dependencies for Flask but required for Superset, see https://flask.palletsprojects.com/en/stable/installation/#optional-dependencies
|
||||
"python-geohash",
|
||||
"pyarrow>=16.1.0, <17", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693
|
||||
"pyarrow>=16.1.0, <19", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693
|
||||
"pyyaml>=6.0.0, <7.0.0",
|
||||
"PyJWT>=2.4.0, <3.0",
|
||||
"redis>=4.6.0, <5.0",
|
||||
@@ -99,7 +100,7 @@ dependencies = [
|
||||
"slack_sdk>=3.19.0, <4",
|
||||
"sqlalchemy>=1.4, <2",
|
||||
"sqlalchemy-utils>=0.38.3, <0.39",
|
||||
"sqlglot>=27.3.0, <28",
|
||||
"sqlglot>=27.15.2, <28",
|
||||
# newer pandas needs 0.9+
|
||||
"tabulate>=0.9.0, <1.0",
|
||||
"typing-extensions>=4, <5",
|
||||
@@ -313,6 +314,7 @@ select = [
|
||||
"E",
|
||||
"F",
|
||||
"F",
|
||||
"G",
|
||||
"I",
|
||||
"N",
|
||||
"PT",
|
||||
@@ -328,6 +330,7 @@ ignore = [
|
||||
"PT006",
|
||||
"T201",
|
||||
"N999",
|
||||
"G201",
|
||||
]
|
||||
extend-select = ["I"]
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ alembic==1.15.2
|
||||
# via flask-migrate
|
||||
amqp==5.3.1
|
||||
# via kombu
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
apispec==6.6.1
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
@@ -114,11 +116,11 @@ flask==2.3.3
|
||||
# flask-session
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
flask-appbuilder==4.8.1
|
||||
flask-appbuilder==5.0.0
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
flask-babel==2.0.0
|
||||
flask-babel==3.1.0
|
||||
# via flask-appbuilder
|
||||
flask-caching==2.3.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
@@ -158,6 +160,7 @@ greenlet==3.1.1
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# shillelagh
|
||||
# sqlalchemy
|
||||
gunicorn==23.0.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
h11==0.16.0
|
||||
@@ -264,7 +267,7 @@ packaging==25.0
|
||||
# limits
|
||||
# marshmallow
|
||||
# shillelagh
|
||||
pandas==2.0.3
|
||||
pandas==2.1.4
|
||||
# via apache-superset (pyproject.toml)
|
||||
paramiko==3.5.1
|
||||
# via
|
||||
@@ -296,6 +299,10 @@ pyasn1-modules==0.4.2
|
||||
# via google-auth
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pydantic==2.11.7
|
||||
# via apache-superset (pyproject.toml)
|
||||
pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
pygments==2.19.1
|
||||
# via rich
|
||||
pyjwt==2.10.1
|
||||
@@ -388,7 +395,7 @@ sqlalchemy-utils==0.38.3
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# flask-appbuilder
|
||||
sqlglot==27.3.0
|
||||
sqlglot==27.15.2
|
||||
# via apache-superset (pyproject.toml)
|
||||
sshtunnel==0.4.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
@@ -406,10 +413,15 @@ typing-extensions==4.14.0
|
||||
# alembic
|
||||
# cattrs
|
||||
# limits
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# pyopenssl
|
||||
# referencing
|
||||
# selenium
|
||||
# shillelagh
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
# via pydantic
|
||||
tzdata==2025.2
|
||||
# via
|
||||
# kombu
|
||||
|
||||
@@ -18,6 +18,10 @@ amqp==5.3.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# kombu
|
||||
annotated-types==0.7.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pydantic
|
||||
apispec==6.6.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -208,12 +212,12 @@ flask==2.3.3
|
||||
# flask-sqlalchemy
|
||||
# flask-testing
|
||||
# flask-wtf
|
||||
flask-appbuilder==4.8.1
|
||||
flask-appbuilder==5.0.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# apache-superset-core
|
||||
flask-babel==2.0.0
|
||||
flask-babel==3.1.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-appbuilder
|
||||
@@ -327,6 +331,7 @@ greenlet==3.1.1
|
||||
# apache-superset
|
||||
# gevent
|
||||
# shillelagh
|
||||
# sqlalchemy
|
||||
grpcio==1.71.0
|
||||
# via
|
||||
# apache-superset
|
||||
@@ -532,7 +537,7 @@ packaging==25.0
|
||||
# pytest
|
||||
# shillelagh
|
||||
# sqlalchemy-bigquery
|
||||
pandas==2.0.3
|
||||
pandas==2.1.4
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -632,6 +637,14 @@ pycparser==2.22
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# cffi
|
||||
pydantic==2.11.7
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
pydantic-core==2.33.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pydantic
|
||||
pydata-google-auth==1.9.0
|
||||
# via pandas-gbq
|
||||
pydruid==0.6.9
|
||||
@@ -835,7 +848,7 @@ sqlalchemy-utils==0.38.3
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# flask-appbuilder
|
||||
sqlglot==27.3.0
|
||||
sqlglot==27.15.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -875,10 +888,17 @@ typing-extensions==4.14.0
|
||||
# apache-superset
|
||||
# cattrs
|
||||
# limits
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# pyopenssl
|
||||
# referencing
|
||||
# selenium
|
||||
# shillelagh
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pydantic
|
||||
tzdata==2025.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
|
||||
@@ -42,7 +42,7 @@ classifiers = [
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
dependencies = [
|
||||
"flask-appbuilder>=4.5.3, <5.0.0",
|
||||
"flask-appbuilder>=5.0.0,<6",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
426
superset-embedded-sdk/package-lock.json
generated
426
superset-embedded-sdk/package-lock.json
generated
@@ -19,7 +19,6 @@
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.5.4",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.1.3",
|
||||
"jest": "^29.7.0",
|
||||
"tscw-config": "^1.1.2",
|
||||
@@ -3233,24 +3232,6 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
|
||||
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
@@ -3644,19 +3625,6 @@
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -3843,18 +3811,6 @@
|
||||
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||
@@ -4045,15 +4001,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-newline": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||
@@ -4063,20 +4010,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.19",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
|
||||
@@ -4137,57 +4070,12 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
@@ -4628,42 +4516,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-readdir-recursive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
|
||||
@@ -4717,30 +4569,6 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-package-type": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||
@@ -4750,19 +4578,6 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -4825,18 +4640,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -4865,45 +4668,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -7092,15 +6856,6 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@@ -7414,12 +7169,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
@@ -10724,23 +10473,6 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
|
||||
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
@@ -11023,16 +10755,6 @@
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -11158,15 +10880,6 @@
|
||||
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
|
||||
"dev": true
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||
@@ -11299,29 +11012,12 @@
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"dev": true
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||
"dev": true
|
||||
},
|
||||
"detect-newline": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
|
||||
"dev": true
|
||||
},
|
||||
"dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.5.19",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
|
||||
@@ -11365,45 +11061,12 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true
|
||||
},
|
||||
"es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
@@ -11712,25 +11375,6 @@
|
||||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"fs-readdir-recursive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
|
||||
@@ -11768,40 +11412,12 @@
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"get-package-type": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -11844,12 +11460,6 @@
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true
|
||||
},
|
||||
"gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -11871,30 +11481,6 @@
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -13505,12 +13091,6 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@@ -13742,12 +13322,6 @@
|
||||
"sisteransi": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.5.4",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.1.3",
|
||||
"jest": "^29.7.0",
|
||||
"tscw-config": "^1.1.2",
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const axios = require('axios');
|
||||
const { name, version } = require('./package.json');
|
||||
|
||||
function log(...args) {
|
||||
@@ -36,9 +35,7 @@ function logError(...args) {
|
||||
// npm commands output a bunch of garbage in the edge cases,
|
||||
// and require sending semi-validated strings to the command line,
|
||||
// so let's just use good old http.
|
||||
const { status } = await axios.get(packageUrl, {
|
||||
validateStatus: (status) => true // we literally just want the status so any status is valid
|
||||
});
|
||||
const { status } = await fetch(packageUrl);
|
||||
|
||||
if (status === 200) {
|
||||
log('version already exists on npm, exiting');
|
||||
|
||||
@@ -160,7 +160,9 @@ def test_validate_npm_handles_file_not_found_exception(mock_run, mock_which):
|
||||
def test_validate_npm_does_not_catch_other_subprocess_exceptions(
|
||||
mock_run, mock_which, exception_type
|
||||
):
|
||||
"""Test validate_npm does not catch OSError and PermissionError (they propagate up)."""
|
||||
"""
|
||||
Test validate_npm does not catch OSError and PermissionError (they propagate up).
|
||||
"""
|
||||
mock_which.return_value = "/usr/bin/npm"
|
||||
mock_run.side_effect = exception_type("Test error")
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ module.exports = {
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:react-prefer-function-component/recommended',
|
||||
'plugin:storybook/recommended',
|
||||
'plugin:react-you-might-not-need-an-effect/legacy-recommended',
|
||||
],
|
||||
parser: '@babel/eslint-parser',
|
||||
parserOptions: {
|
||||
@@ -323,6 +324,7 @@ module.exports = {
|
||||
'*.stories.tsx',
|
||||
'*.stories.jsx',
|
||||
'fixtures.*',
|
||||
'playwright/**/*',
|
||||
],
|
||||
excludedFiles: 'cypress-base/cypress/**/*',
|
||||
plugins: ['jest', 'jest-dom', 'no-only-tests', 'testing-library'],
|
||||
@@ -397,6 +399,13 @@ module.exports = {
|
||||
'react/no-void-elements': 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['playwright/**/*'],
|
||||
rules: {
|
||||
'import/no-unresolved': 0, // Playwright is not installed in main build
|
||||
'import/no-extraneous-dependencies': 0, // Playwright is not installed in main build
|
||||
},
|
||||
},
|
||||
],
|
||||
// eslint-disable-next-line no-dupe-keys
|
||||
rules: {
|
||||
@@ -404,13 +413,6 @@ module.exports = {
|
||||
'icons/no-fa-icons-usage': 'error',
|
||||
'i18n-strings/no-template-vars': ['error', true],
|
||||
'i18n-strings/sentence-case-buttons': 'error',
|
||||
camelcase: [
|
||||
'error',
|
||||
{
|
||||
allow: ['^UNSAFE_'],
|
||||
properties: 'never',
|
||||
},
|
||||
],
|
||||
'class-methods-use-this': 0,
|
||||
curly: 2,
|
||||
'func-names': 0,
|
||||
|
||||
1
superset-frontend/.gitignore
vendored
1
superset-frontend/.gitignore
vendored
@@ -3,3 +3,4 @@ cypress/screenshots
|
||||
cypress/videos
|
||||
src/temp
|
||||
.temp_cache/
|
||||
.tsbuildinfo
|
||||
|
||||
@@ -175,7 +175,7 @@ describe('Charts list', () => {
|
||||
interceptDelete();
|
||||
cy.getBySel('sort-header').contains('Name').click();
|
||||
|
||||
// Modal closes immediatly without this
|
||||
// Modal closes immediately without this
|
||||
cy.wait(2000);
|
||||
|
||||
cy.getBySel('table-row').eq(0).contains('3 - Sample chart');
|
||||
|
||||
@@ -1,766 +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.
|
||||
*/
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Interception } from 'cypress/types/net-stubbing';
|
||||
import { waitForChartLoad } from 'cypress/utils';
|
||||
import { SUPPORTED_CHARTS_DASHBOARD } from 'cypress/utils/urls';
|
||||
import {
|
||||
openTopLevelTab,
|
||||
SUPPORTED_TIER1_CHARTS,
|
||||
SUPPORTED_TIER2_CHARTS,
|
||||
} from './utils';
|
||||
import {
|
||||
interceptExploreJson,
|
||||
interceptV1ChartData,
|
||||
interceptFormDataKey,
|
||||
} from '../explore/utils';
|
||||
|
||||
const interceptDrillInfo = () => {
|
||||
cy.intercept('GET', '**/api/v1/dataset/*/drill_info/*', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
result: {
|
||||
id: 1,
|
||||
changed_on_humanized: '2 days ago',
|
||||
created_on_humanized: 'a week ago',
|
||||
table_name: 'birth_names',
|
||||
changed_by: {
|
||||
first_name: 'Admin',
|
||||
last_name: 'User',
|
||||
},
|
||||
created_by: {
|
||||
first_name: 'Admin',
|
||||
last_name: 'User',
|
||||
},
|
||||
owners: [
|
||||
{
|
||||
first_name: 'Admin',
|
||||
last_name: 'User',
|
||||
},
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
column_name: 'gender',
|
||||
verbose_name: null,
|
||||
},
|
||||
{
|
||||
column_name: 'state',
|
||||
verbose_name: null,
|
||||
},
|
||||
{
|
||||
column_name: 'name',
|
||||
verbose_name: null,
|
||||
},
|
||||
{
|
||||
column_name: 'ds',
|
||||
verbose_name: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}).as('drillInfo');
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
cy.get('body').then($body => {
|
||||
if ($body.find('[data-test="close-drill-by-modal"]').length) {
|
||||
cy.getBySel('close-drill-by-modal').click({ force: true });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const openTableContextMenu = (
|
||||
cellContent: string,
|
||||
tableSelector = "[data-test-viz-type='table']",
|
||||
) => {
|
||||
cy.get(tableSelector).scrollIntoView();
|
||||
cy.get(tableSelector).contains(cellContent).first().rightclick();
|
||||
};
|
||||
|
||||
const drillBy = (targetDrillByColumn: string, isLegacy = false) => {
|
||||
if (isLegacy) {
|
||||
interceptExploreJson('legacyData');
|
||||
} else {
|
||||
interceptV1ChartData();
|
||||
}
|
||||
|
||||
cy.get('.ant-dropdown:not(.ant-dropdown-hidden)', { timeout: 15000 })
|
||||
.should('be.visible')
|
||||
.find("[role='menu'] [role='menuitem']")
|
||||
.contains(/^Drill by$/)
|
||||
.trigger('mouseover', { force: true });
|
||||
|
||||
cy.get(
|
||||
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
|
||||
{ timeout: 15000 },
|
||||
)
|
||||
.should('be.visible')
|
||||
.find('[role="menuitem"]')
|
||||
.contains(new RegExp(`^${targetDrillByColumn}$`))
|
||||
.click();
|
||||
|
||||
cy.get(
|
||||
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
|
||||
).trigger('mouseout', { clientX: 0, clientY: 0, force: true });
|
||||
|
||||
cy.get(
|
||||
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
|
||||
).should('not.exist');
|
||||
|
||||
if (isLegacy) {
|
||||
return cy.wait('@legacyData');
|
||||
}
|
||||
return cy.wait('@v1Data');
|
||||
};
|
||||
|
||||
const verifyExpectedFormData = (
|
||||
interceptedRequest: Interception,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
expectedFormData: Record<string, any>,
|
||||
) => {
|
||||
const actualFormData = interceptedRequest.request.body?.form_data;
|
||||
Object.entries(expectedFormData).forEach(([key, val]) => {
|
||||
expect(actualFormData?.[key]).to.eql(val);
|
||||
});
|
||||
};
|
||||
|
||||
const testEchart = (
|
||||
vizType: string,
|
||||
chartName: string,
|
||||
drillClickCoordinates: [[number, number], [number, number]],
|
||||
furtherDrillDimension = 'name',
|
||||
) => {
|
||||
cy.get(`[data-test-viz-type='${vizType}'] canvas`).then($canvas => {
|
||||
// click 'boy'
|
||||
cy.wrap($canvas).scrollIntoView();
|
||||
cy.wrap($canvas).trigger(
|
||||
'mouseover',
|
||||
drillClickCoordinates[0][0],
|
||||
drillClickCoordinates[0][1],
|
||||
);
|
||||
cy.wrap($canvas).rightclick(
|
||||
drillClickCoordinates[0][0],
|
||||
drillClickCoordinates[0][1],
|
||||
);
|
||||
|
||||
drillBy('state').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['state'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.getBySel(`"Drill by: ${chartName}-modal"`).as('drillByModal');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.draggable-trigger')
|
||||
.should('contain', chartName);
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'state');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
|
||||
// further drill
|
||||
cy.get(`[data-test="drill-by-chart"] canvas`).then($canvas => {
|
||||
// click 'other'
|
||||
cy.wrap($canvas).scrollIntoView();
|
||||
cy.wrap($canvas).trigger(
|
||||
'mouseover',
|
||||
drillClickCoordinates[1][0],
|
||||
drillClickCoordinates[1][1],
|
||||
);
|
||||
cy.wrap($canvas).rightclick(
|
||||
drillClickCoordinates[1][0],
|
||||
drillClickCoordinates[1][1],
|
||||
);
|
||||
|
||||
drillBy(furtherDrillDimension).then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: [furtherDrillDimension],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'other',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'state',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
|
||||
// undo - back to drill by state
|
||||
interceptV1ChartData('drillByUndo');
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'state (other)')
|
||||
.and('contain', furtherDrillDimension)
|
||||
.contains('state (other)')
|
||||
.click();
|
||||
cy.wait('@drillByUndo').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['state'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('not.contain', 'state (other)')
|
||||
.and('not.contain', furtherDrillDimension)
|
||||
.and('contain', 'state');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('Drill by modal', () => {
|
||||
beforeEach(() => {
|
||||
closeModal();
|
||||
});
|
||||
before(() => {
|
||||
interceptDrillInfo();
|
||||
cy.visit(SUPPORTED_CHARTS_DASHBOARD);
|
||||
});
|
||||
|
||||
describe('Modal actions + Table', () => {
|
||||
before(() => {
|
||||
closeModal();
|
||||
interceptDrillInfo();
|
||||
openTopLevelTab('Tier 1');
|
||||
SUPPORTED_TIER1_CHARTS.forEach(waitForChartLoad);
|
||||
});
|
||||
|
||||
it.only('opens the modal from the context menu', () => {
|
||||
openTableContextMenu('boy');
|
||||
drillBy('state').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['state'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.getBySel('"Drill by: Table-modal"').as('drillByModal');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.draggable-trigger')
|
||||
.should('contain', 'Drill by: Table');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="metadata-bar"]')
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'state');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('contain', 'state')
|
||||
.and('contain', 'sum__num');
|
||||
|
||||
// further drilling
|
||||
openTableContextMenu('CA', '[data-test="drill-by-chart"]');
|
||||
drillBy('name').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['name'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'CA',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'state',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('not.contain', 'state')
|
||||
.and('contain', 'name')
|
||||
.and('contain', 'sum__num');
|
||||
|
||||
// undo - back to drill by state
|
||||
interceptV1ChartData('drillByUndo');
|
||||
interceptFormDataKey();
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'state (CA)')
|
||||
.and('contain', 'name')
|
||||
.contains('state (CA)')
|
||||
.click();
|
||||
cy.wait('@drillByUndo').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupby: ['state'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('not.contain', 'name')
|
||||
.and('contain', 'state')
|
||||
.and('contain', 'sum__num');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('not.contain', 'state (CA)')
|
||||
.and('not.contain', 'name')
|
||||
.and('contain', 'state');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-display-toggle"]')
|
||||
.contains('Table')
|
||||
.click();
|
||||
|
||||
cy.getBySel('drill-by-chart').should('not.exist');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-results-table"]')
|
||||
.should('be.visible');
|
||||
|
||||
cy.wait('@formDataKey').then(intercept => {
|
||||
cy.get('@drillByModal')
|
||||
.contains('Edit chart')
|
||||
.should('have.attr', 'href')
|
||||
.and(
|
||||
'contain',
|
||||
`/explore/?form_data_key=${intercept.response?.body?.key}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tier 1 charts', () => {
|
||||
before(() => {
|
||||
closeModal();
|
||||
interceptDrillInfo();
|
||||
openTopLevelTab('Tier 1');
|
||||
SUPPORTED_TIER1_CHARTS.forEach(waitForChartLoad);
|
||||
});
|
||||
|
||||
it('Pivot Table', () => {
|
||||
openTableContextMenu('boy', "[data-test-viz-type='pivot_table_v2']");
|
||||
drillBy('name').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupbyRows: ['state'],
|
||||
groupbyColumns: ['name'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.getBySel('"Drill by: Pivot Table-modal"').as('drillByModal');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.draggable-trigger')
|
||||
.should('contain', 'Drill by: Pivot Table');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'name');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('contain', 'state')
|
||||
.and('contain', 'name')
|
||||
.and('contain', 'sum__num')
|
||||
.and('not.contain', 'Gender');
|
||||
|
||||
openTableContextMenu('CA', '[data-test="drill-by-chart"]');
|
||||
drillBy('ds').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupbyColumns: ['name'],
|
||||
groupbyRows: ['ds'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'CA',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'state',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('contain', 'name')
|
||||
.and('contain', 'ds')
|
||||
.and('contain', 'sum__num')
|
||||
.and('not.contain', 'state');
|
||||
|
||||
interceptV1ChartData('drillByUndo');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'name (CA)')
|
||||
.and('contain', 'ds')
|
||||
.contains('name (CA)')
|
||||
.click();
|
||||
cy.wait('@drillByUndo').then(intercepted => {
|
||||
verifyExpectedFormData(intercepted, {
|
||||
groupbyRows: ['state'],
|
||||
groupbyColumns: ['name'],
|
||||
adhoc_filters: [
|
||||
{
|
||||
clause: 'WHERE',
|
||||
comparator: 'boy',
|
||||
expressionType: 'SIMPLE',
|
||||
operator: '==',
|
||||
operatorId: 'EQUALS',
|
||||
subject: 'gender',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible')
|
||||
.and('not.contain', 'ds')
|
||||
.and('contain', 'state')
|
||||
.and('contain', 'name')
|
||||
.and('contain', 'sum__num');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('not.contain', 'name (CA)')
|
||||
.and('not.contain', 'ds')
|
||||
.and('contain', 'name');
|
||||
});
|
||||
|
||||
it('Line chart', () => {
|
||||
testEchart('echarts_timeseries_line', 'Line Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Area Chart', () => {
|
||||
testEchart('echarts_area', 'Area Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Scatter Chart', () => {
|
||||
testEchart('echarts_timeseries_scatter', 'Scatter Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip('Bar Chart', () => {
|
||||
testEchart('echarts_timeseries_bar', 'Bar Chart', [
|
||||
[85, 94],
|
||||
[490, 68],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Pie Chart', () => {
|
||||
testEchart('pie', 'Pie Chart', [
|
||||
[243, 167],
|
||||
[534, 248],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tier 2 charts', () => {
|
||||
before(() => {
|
||||
closeModal();
|
||||
interceptDrillInfo();
|
||||
openTopLevelTab('Tier 2');
|
||||
SUPPORTED_TIER2_CHARTS.forEach(waitForChartLoad);
|
||||
});
|
||||
|
||||
it('Box Plot Chart', () => {
|
||||
testEchart(
|
||||
'box_plot',
|
||||
'Box Plot Chart',
|
||||
[
|
||||
[139, 277],
|
||||
[787, 441],
|
||||
],
|
||||
'ds',
|
||||
);
|
||||
});
|
||||
|
||||
it('Generic Chart', () => {
|
||||
testEchart('echarts_timeseries', 'Generic Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Smooth Line Chart', () => {
|
||||
testEchart('echarts_timeseries_smooth', 'Smooth Line Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Step Line Chart', () => {
|
||||
testEchart('echarts_timeseries_step', 'Step Line Chart', [
|
||||
[85, 93],
|
||||
[85, 93],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Funnel Chart', () => {
|
||||
testEchart('funnel', 'Funnel Chart', [
|
||||
[154, 80],
|
||||
[421, 39],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Gauge Chart', () => {
|
||||
testEchart('gauge_chart', 'Gauge Chart', [
|
||||
[151, 95],
|
||||
[300, 143],
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip('Radar Chart', () => {
|
||||
testEchart('radar', 'Radar Chart', [
|
||||
[182, 49],
|
||||
[423, 91],
|
||||
]);
|
||||
});
|
||||
|
||||
it('Treemap V2 Chart', () => {
|
||||
testEchart('treemap_v2', 'Treemap V2 Chart', [
|
||||
[145, 84],
|
||||
[220, 105],
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip('Mixed Chart', () => {
|
||||
cy.get('[data-test-viz-type="mixed_timeseries"] canvas').then($canvas => {
|
||||
// click 'boy'
|
||||
cy.wrap($canvas).scrollIntoView();
|
||||
cy.wrap($canvas).trigger('mouseover', 85, 93);
|
||||
cy.wrap($canvas).rightclick(85, 93);
|
||||
|
||||
drillBy('name').then(intercepted => {
|
||||
const { queries } = intercepted.request.body;
|
||||
expect(queries[0].columns).to.eql(['name']);
|
||||
expect(queries[0].filters).to.eql([
|
||||
{ col: 'gender', op: '==', val: 'boy' },
|
||||
]);
|
||||
expect(queries[1].columns).to.eql(['state']);
|
||||
expect(queries[1].filters).to.eql([]);
|
||||
});
|
||||
|
||||
cy.getBySel('"Drill by: Mixed Chart-modal"').as('drillByModal');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.draggable-trigger')
|
||||
.should('contain', 'Mixed Chart');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'name');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
|
||||
// further drill
|
||||
cy.get(`[data-test="drill-by-chart"] canvas`).then($canvas => {
|
||||
// click second query
|
||||
cy.wrap($canvas).scrollIntoView();
|
||||
cy.wrap($canvas).trigger('mouseover', 261, 114);
|
||||
cy.wrap($canvas).rightclick(261, 114);
|
||||
|
||||
drillBy('ds').then(intercepted => {
|
||||
const { queries } = intercepted.request.body;
|
||||
expect(queries[0].columns).to.eql(['name']);
|
||||
expect(queries[0].filters).to.eql([
|
||||
{ col: 'gender', op: '==', val: 'boy' },
|
||||
]);
|
||||
expect(queries[1].columns).to.eql(['ds']);
|
||||
expect(queries[1].filters).to.eql([
|
||||
{ col: 'state', op: '==', val: 'other' },
|
||||
]);
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
|
||||
// undo - back to drill by state
|
||||
interceptV1ChartData('drillByUndo');
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('contain', 'name (other)')
|
||||
.and('contain', 'ds')
|
||||
.contains('name (other)')
|
||||
.click();
|
||||
|
||||
cy.wait('@drillByUndo').then(intercepted => {
|
||||
const { queries } = intercepted.request.body;
|
||||
expect(queries[0].columns).to.eql(['name']);
|
||||
expect(queries[0].filters).to.eql([
|
||||
{ col: 'gender', op: '==', val: 'boy' },
|
||||
]);
|
||||
expect(queries[1].columns).to.eql(['state']);
|
||||
expect(queries[1].filters).to.eql([]);
|
||||
});
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('.ant-breadcrumb')
|
||||
.should('be.visible')
|
||||
.and('contain', 'gender (boy)')
|
||||
.and('contain', '/')
|
||||
.and('not.contain', 'name (other)')
|
||||
.and('not.contain', 'ds')
|
||||
.and('contain', 'name');
|
||||
|
||||
cy.get('@drillByModal')
|
||||
.find('[data-test="drill-by-chart"]')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,186 +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.
|
||||
*/
|
||||
// ***********************************************
|
||||
// Tests for setting controls in the UI
|
||||
// ***********************************************
|
||||
import { interceptChart, setSelectSearchInput } from 'cypress/utils';
|
||||
|
||||
describe('Datasource control', () => {
|
||||
const newMetricName = `abc${Date.now()}`;
|
||||
|
||||
it('should allow edit dataset', () => {
|
||||
interceptChart({ legacy: false }).as('chartData');
|
||||
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
|
||||
cy.get('[data-test="datasource-menu-trigger"]').click();
|
||||
|
||||
cy.get('[data-test="edit-dataset"]').click();
|
||||
|
||||
cy.get('[data-test="edit-dataset-tabs"]').within(() => {
|
||||
cy.contains('Metrics').click();
|
||||
});
|
||||
// create new metric
|
||||
cy.get('[data-test="crud-add-table-item"]', { timeout: 10000 }).click();
|
||||
cy.wait(1000);
|
||||
cy.get('.ant-table-body [data-test="textarea-editable-title-input"]')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
cy.get('.ant-table-body [data-test="textarea-editable-title-input"]')
|
||||
.first()
|
||||
.focus();
|
||||
cy.focused().clear({ force: true });
|
||||
cy.focused().type(`${newMetricName}{enter}`, { force: true });
|
||||
|
||||
cy.get('[data-test="datasource-modal-save"]').click();
|
||||
cy.get('.ant-modal-confirm-btns button').contains('OK').click();
|
||||
// select new metric
|
||||
cy.get('[data-test=metrics]')
|
||||
.contains('Drop columns/metrics here or click')
|
||||
.click();
|
||||
|
||||
cy.get('input[aria-label="Select saved metrics"]')
|
||||
.should('exist')
|
||||
.then($input => {
|
||||
setSelectSearchInput($input, newMetricName);
|
||||
});
|
||||
|
||||
// delete metric
|
||||
cy.get('[data-test="datasource-menu-trigger"]').click();
|
||||
cy.get('[data-test="edit-dataset"]').click();
|
||||
cy.get('.ant-modal-content').within(() => {
|
||||
cy.get('[data-test="collection-tab-Metrics"]')
|
||||
.contains('Metrics')
|
||||
.click();
|
||||
});
|
||||
cy.get(`[data-test="textarea-editable-title-input"]`)
|
||||
.contains(newMetricName)
|
||||
.closest('tr')
|
||||
.find('[data-test="crud-delete-icon"]')
|
||||
.click();
|
||||
cy.get('[data-test="datasource-modal-save"]').click();
|
||||
cy.get('.ant-modal-confirm-btns button').contains('OK').click();
|
||||
cy.get('[data-test="metrics"]').contains(newMetricName).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Color scheme control', () => {
|
||||
beforeEach(() => {
|
||||
interceptChart({ legacy: false }).as('chartData');
|
||||
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
});
|
||||
|
||||
it('should show color options with and without tooltips', () => {
|
||||
cy.get('#controlSections-tab-CUSTOMIZE').click();
|
||||
cy.get('.ant-select-selection-item .color-scheme-label').contains(
|
||||
'Superset Colors',
|
||||
);
|
||||
cy.get('.ant-select-selection-item .color-scheme-label').trigger(
|
||||
'mouseover',
|
||||
);
|
||||
cy.get('.color-scheme-tooltip').should('be.visible');
|
||||
cy.get('.color-scheme-tooltip').contains('Superset Colors');
|
||||
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
|
||||
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
|
||||
|
||||
cy.get('.color-scheme-label')
|
||||
.contains('Superset Colors')
|
||||
.trigger('mouseover');
|
||||
|
||||
cy.get('.color-scheme-label')
|
||||
.contains('Superset Colors')
|
||||
.trigger('mouseout');
|
||||
|
||||
cy.focused().type('lyftColors');
|
||||
cy.getBySel('lyftColors').should('exist');
|
||||
cy.getBySel('lyftColors').trigger('mouseover', { force: true });
|
||||
cy.get('.color-scheme-tooltip').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
describe('VizType control', () => {
|
||||
beforeEach(() => {
|
||||
interceptChart({ legacy: false }).as('tableChartData');
|
||||
interceptChart({ legacy: false }).as('bigNumberChartData');
|
||||
});
|
||||
|
||||
it('Can change vizType', () => {
|
||||
cy.visitChartByName('Daily Totals');
|
||||
cy.verifySliceSuccess({ waitAlias: '@tableChartData' });
|
||||
|
||||
cy.contains('View all charts').click();
|
||||
|
||||
cy.get('.ant-modal-content').within(() => {
|
||||
cy.get('button').contains('KPI').click(); // change categories
|
||||
cy.get('[role="button"]').contains('Big Number').click();
|
||||
cy.get('button').contains('Select').click();
|
||||
});
|
||||
|
||||
cy.get('button[data-test="run-query-button"]').click();
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@bigNumberChartData',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test datatable', () => {
|
||||
beforeEach(() => {
|
||||
interceptChart({ legacy: false }).as('tableChartData');
|
||||
interceptChart({ legacy: false }).as('lineChartData');
|
||||
cy.visitChartByName('Daily Totals');
|
||||
});
|
||||
it('Data Pane opens and loads results', () => {
|
||||
cy.contains('Results').click();
|
||||
cy.get('[data-test="row-count-label"]').contains('26 rows');
|
||||
cy.get('.ant-empty-description').should('not.exist');
|
||||
});
|
||||
it('Datapane loads view samples', () => {
|
||||
cy.intercept(
|
||||
'**/datasource/samples?force=false&datasource_type=table&datasource_id=*',
|
||||
).as('Samples');
|
||||
cy.contains('Samples').click();
|
||||
cy.wait('@Samples');
|
||||
cy.get('.ant-tabs-tab-active').contains('Samples');
|
||||
cy.get('[data-test="row-count-label"]').contains('1k rows');
|
||||
cy.get('.ant-empty-description').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Groupby control', () => {
|
||||
it('Set groupby', () => {
|
||||
interceptChart({ legacy: false }).as('chartData');
|
||||
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
|
||||
cy.get('[data-test=groupby]')
|
||||
.contains('Drop columns here or click')
|
||||
.click();
|
||||
cy.get('[id="adhoc-metric-edit-tabs-tab-simple"]').click();
|
||||
cy.get('input[aria-label="Column"]').click();
|
||||
cy.get('input[aria-label="Column"]').type('state{enter}');
|
||||
cy.get('[data-test="ColumnEdit#save"]').contains('Save').click();
|
||||
|
||||
cy.get('button[data-test="run-query-button"]').click();
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
});
|
||||
});
|
||||
@@ -33,6 +33,7 @@ module.exports = {
|
||||
'^@superset-ui/([^/]+)$': '<rootDir>/node_modules/@superset-ui/$1/src',
|
||||
// mapping @apache-superset/core to local package
|
||||
'^@apache-superset/core$': '<rootDir>/packages/superset-core/src',
|
||||
'^@apache-superset/core/(.*)$': '<rootDir>/packages/superset-core/src/$1',
|
||||
},
|
||||
testEnvironment: '<rootDir>/spec/helpers/jsDomWithFetchAPI.ts',
|
||||
modulePathIgnorePatterns: ['<rootDir>/packages/generator-superset'],
|
||||
|
||||
200
superset-frontend/package-lock.json
generated
200
superset-frontend/package-lock.json
generated
@@ -21,7 +21,7 @@
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@rjsf/core": "^5.24.13",
|
||||
"@rjsf/utils": "^5.24.3",
|
||||
"@rjsf/validator-ajv8": "^5.24.12",
|
||||
"@rjsf/validator-ajv8": "^5.24.13",
|
||||
"@scarf/scarf": "^1.4.0",
|
||||
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
|
||||
"@superset-ui/core": "file:./packages/superset-ui-core",
|
||||
@@ -54,16 +54,18 @@
|
||||
"@visx/scale": "^3.5.0",
|
||||
"@visx/tooltip": "^3.0.0",
|
||||
"@visx/xychart": "^3.5.1",
|
||||
"ag-grid-community": "34.2.0",
|
||||
"ag-grid-react": "34.2.0",
|
||||
"antd": "^5.24.6",
|
||||
"chrono-node": "^2.7.8",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^0.5.4",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-scale": "^2.1.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"dom-to-image-more": "^3.6.0",
|
||||
"dom-to-pdf": "^0.3.2",
|
||||
"echarts": "^5.6.0",
|
||||
"emotion-rgba": "0.0.12",
|
||||
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -159,6 +161,7 @@
|
||||
"@hot-loader/react-dom": "^17.0.2",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
||||
"@playwright/test": "^1.49.1",
|
||||
"@storybook/addon-actions": "8.1.11",
|
||||
"@storybook/addon-controls": "8.1.11",
|
||||
"@storybook/addon-essentials": "8.1.11",
|
||||
@@ -174,6 +177,7 @@
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/content-disposition": "^0.5.9",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/js-levenshtein": "^1.1.3",
|
||||
@@ -189,7 +193,6 @@
|
||||
"@types/react-resizable": "^3.0.8",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"@types/react-ultimate-pagination": "^1.2.4",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.8",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/redux-localstorage": "^1.0.8",
|
||||
@@ -229,6 +232,7 @@
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-prefer-function-component": "^3.3.0",
|
||||
"eslint-plugin-react-you-might-not-need-an-effect": "^0.5.1",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"eslint-plugin-testing-library": "^6.4.0",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
@@ -8882,9 +8886,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@ndelangen/get-tarball/node_modules/tar-fs": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
|
||||
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10109,6 +10113,22 @@
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz",
|
||||
"integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.55.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@pnpm/config.env-replace": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
|
||||
@@ -10884,9 +10904,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rjsf/validator-ajv8": {
|
||||
"version": "5.24.12",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.24.12.tgz",
|
||||
"integrity": "sha512-IMXdCjvDNdvb+mDgZC3AlAtr0pjYKq5s0GcLECjG5PuiX7Ib4JaDQHZY5ZJdKblMfgzhsn8AAOi573jXAt7BHQ==",
|
||||
"version": "5.24.13",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.24.13.tgz",
|
||||
"integrity": "sha512-oWHP7YK581M8I5cF1t+UXFavnv+bhcqjtL1a7MG/Kaffi0EwhgcYjODrD8SsnrhncsEYMqSECr4ZOEoirnEUWw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ajv": "^8.12.0",
|
||||
@@ -15250,6 +15270,13 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/content-disposition": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz",
|
||||
"integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/conventional-commits-parser": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz",
|
||||
@@ -16069,16 +16096,6 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-ultimate-pagination": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-ultimate-pagination/-/react-ultimate-pagination-1.2.4.tgz",
|
||||
"integrity": "sha512-1y9jLt3KEFGzFD+99qVpJUI/Eu4cEx48sClB957eGoepWRLVVi+r1UBj0157Mg7HYZcIF4I1/qGZYaBBQWhaqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-virtualized-auto-sizer": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.8.tgz",
|
||||
@@ -18699,27 +18716,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ag-charts-types": {
|
||||
"version": "12.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-12.0.2.tgz",
|
||||
"integrity": "sha512-AWM1Y+XW+9VMmV3AbzdVEnreh/I2C9Pmqpc2iLmtId3Xbvmv7O56DqnuDb9EXjK5uPxmyUerTP+utL13UGcztw==",
|
||||
"version": "12.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-12.2.0.tgz",
|
||||
"integrity": "sha512-d2qQrQirt9wP36YW5HPuOvXsiajyiFnr1CTsoCbs02bavPDz7Lk2jHp64+waM4YKgXb3GN7gafbBI9Qgk33BmQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ag-grid-community": {
|
||||
"version": "34.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-34.0.2.tgz",
|
||||
"integrity": "sha512-hVJp5vrmwHRB10YjfSOVni5YJkO/v+asLjT72S4YnIFSx8lAgyPmByNJgtojk1aJ5h6Up93jTEmGDJeuKiWWLA==",
|
||||
"version": "34.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-34.2.0.tgz",
|
||||
"integrity": "sha512-peS7THEMYwpIrwLQHmkRxw/TlOnddD/F5A88RqlBxf8j+WqVYRWMOOhU5TqymGcha7z2oZ8IoL9ROl3gvtdEjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ag-charts-types": "12.0.2"
|
||||
"ag-charts-types": "12.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ag-grid-react": {
|
||||
"version": "34.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-34.0.2.tgz",
|
||||
"integrity": "sha512-1KBXkTvwtZiYVlSuDzBkiqfHjZgsATOmpLZdAtdmsCSOOOEWai0F9zHHgBuHfyciAE4nrbQWfojkx8IdnwsKFw==",
|
||||
"version": "34.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-34.2.0.tgz",
|
||||
"integrity": "sha512-dLKFw6hz75S0HLuZvtcwjm+gyiI4gXVzHEu7lWNafWAX0mb8DhogEOP5wbzAlsN6iCfi7bK/cgZImZFjenlqwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ag-grid-community": "34.0.2",
|
||||
"ag-grid-community": "34.2.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -21928,7 +21945,6 @@
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
@@ -24469,12 +24485,6 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/emotion-rgba": {
|
||||
"version": "0.0.12",
|
||||
"resolved": "https://registry.npmjs.org/emotion-rgba/-/emotion-rgba-0.0.12.tgz",
|
||||
"integrity": "sha512-lvtZ52BWisYDtis+HctQMkxcHwmFbzTiZhgMJGFfWXLsBYEzthfKE7nlysOiUwmmAdTM/8YBAPfwQ4MEDwiaWw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodable": {
|
||||
"version": "0.7.8",
|
||||
"resolved": "https://registry.npmjs.org/encodable/-/encodable-0.7.8.tgz",
|
||||
@@ -25920,6 +25930,36 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-react-you-might-not-need-an-effect": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-you-might-not-need-an-effect/-/eslint-plugin-react-you-might-not-need-an-effect-0.5.1.tgz",
|
||||
"integrity": "sha512-Gi2kfHLkXUT3j+IAwgb8TEhY10iMwsdwSsgbIxk98zPpuPW7M52ey9fU1oPZrWUlyekr5eXwUCjeTHekS6Isrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eslint-utils": "^3.0.0",
|
||||
"globals": "^16.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8.40.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-you-might-not-need-an-effect/node_modules/globals": {
|
||||
"version": "16.4.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
|
||||
"integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/doctrine": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
||||
@@ -26035,6 +26075,25 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-utils": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
|
||||
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mysticatea"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-visitor-keys": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
|
||||
@@ -45527,6 +45586,53 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz",
|
||||
"integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.55.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz",
|
||||
"integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/png-async": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/png-async/-/png-async-0.9.4.tgz",
|
||||
@@ -60634,7 +60740,7 @@
|
||||
},
|
||||
"packages/superset-core": {
|
||||
"name": "@apache-superset/core",
|
||||
"version": "0.0.1-rc2",
|
||||
"version": "0.0.1-rc5",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.26.4",
|
||||
@@ -60644,7 +60750,8 @@
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@types/react": "^17.0.83",
|
||||
"install": "^0.13.0",
|
||||
"npm": "^11.1.0"
|
||||
"npm": "^11.1.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": "^5.24.6",
|
||||
@@ -63330,10 +63437,10 @@
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@types/react": "*",
|
||||
"lodash": "^4.17.21",
|
||||
"prop-types": "^15.8.1"
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
@@ -63358,14 +63465,15 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@apache-superset/core": "*",
|
||||
"@babel/runtime": "^7.28.2",
|
||||
"@fontsource/fira-code": "^5.2.6",
|
||||
"@fontsource/inter": "^5.2.6",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@visx/responsive": "^3.12.0",
|
||||
"ace-builds": "^1.43.1",
|
||||
"ag-grid-community": "^34.0.2",
|
||||
"ag-grid-react": "34.0.2",
|
||||
"ag-grid-community": "34.2.0",
|
||||
"ag-grid-react": "34.2.0",
|
||||
"brace": "^0.11.1",
|
||||
"classnames": "^2.2.5",
|
||||
"core-js": "^3.38.1",
|
||||
@@ -65236,6 +65344,8 @@
|
||||
"d3-array": "^1.2.4",
|
||||
"d3-color": "^1.4.1",
|
||||
"d3-scale": "^3.0.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"handlebars": "^4.7.8",
|
||||
"lodash": "^4.17.21",
|
||||
"mousetrap": "^1.6.5",
|
||||
"ngeohash": "^0.6.3",
|
||||
@@ -65402,6 +65512,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
@@ -65453,6 +65564,7 @@
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"echarts": "*",
|
||||
@@ -66630,6 +66742,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.17.11",
|
||||
@@ -67761,6 +67874,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
|
||||
@@ -63,6 +63,11 @@
|
||||
"plugins:release-conventional": "npm run prune && npm run plugins:build && lerna publish --conventional-commits --create-release github --yes",
|
||||
"plugins:release-from-tag": "npm run prune && npm run plugins:build && lerna publish from-package --yes",
|
||||
"plugins:storybook": "cd packages/superset-ui-demo && npm run storybook",
|
||||
"playwright:test": "playwright test",
|
||||
"playwright:ui": "playwright test --ui",
|
||||
"playwright:headed": "playwright test --headed",
|
||||
"playwright:debug": "playwright test --debug",
|
||||
"playwright:report": "playwright show-report",
|
||||
"prettier": "npm run _prettier -- --write",
|
||||
"prettier-check": "npm run _prettier -- --check",
|
||||
"prod": "npm run build",
|
||||
@@ -89,7 +94,7 @@
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@rjsf/core": "^5.24.13",
|
||||
"@rjsf/utils": "^5.24.3",
|
||||
"@rjsf/validator-ajv8": "^5.24.12",
|
||||
"@rjsf/validator-ajv8": "^5.24.13",
|
||||
"@scarf/scarf": "^1.4.0",
|
||||
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
|
||||
"@superset-ui/core": "file:./packages/superset-ui-core",
|
||||
@@ -122,16 +127,18 @@
|
||||
"@visx/scale": "^3.5.0",
|
||||
"@visx/tooltip": "^3.0.0",
|
||||
"@visx/xychart": "^3.5.1",
|
||||
"ag-grid-community": "34.2.0",
|
||||
"ag-grid-react": "34.2.0",
|
||||
"antd": "^5.24.6",
|
||||
"chrono-node": "^2.7.8",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^0.5.4",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-scale": "^2.1.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"dom-to-image-more": "^3.6.0",
|
||||
"dom-to-pdf": "^0.3.2",
|
||||
"echarts": "^5.6.0",
|
||||
"emotion-rgba": "0.0.12",
|
||||
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -227,6 +234,7 @@
|
||||
"@hot-loader/react-dom": "^17.0.2",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
||||
"@playwright/test": "^1.49.1",
|
||||
"@storybook/addon-actions": "8.1.11",
|
||||
"@storybook/addon-controls": "8.1.11",
|
||||
"@storybook/addon-essentials": "8.1.11",
|
||||
@@ -242,6 +250,7 @@
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/content-disposition": "^0.5.9",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/js-levenshtein": "^1.1.3",
|
||||
@@ -257,7 +266,6 @@
|
||||
"@types/react-resizable": "^3.0.8",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"@types/react-ultimate-pagination": "^1.2.4",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.8",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/redux-localstorage": "^1.0.8",
|
||||
@@ -297,6 +305,7 @@
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-prefer-function-component": "^3.3.0",
|
||||
"eslint-plugin-react-you-might-not-need-an-effect": "^0.5.1",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"eslint-plugin-testing-library": "^6.4.0",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
|
||||
@@ -22,19 +22,6 @@ To add the package to Superset, go to the `superset-frontend` subdirectory in yo
|
||||
npm i -S ../../<%= packageName %>
|
||||
```
|
||||
|
||||
If your Superset plugin exists in the `superset-frontend` directory and you wish to resolve TypeScript errors about `@superset-ui/core` not being resolved correctly, add the following to your `tsconfig.json` file:
|
||||
|
||||
```
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/superset-ui-chart-controls"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/superset-ui-core"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
You may also wish to add the following to the `include` array in `tsconfig.json` to make Superset types available to your plugin:
|
||||
|
||||
```
|
||||
|
||||
@@ -1,44 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "lib",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": false,
|
||||
"jsx": "react",
|
||||
"lib": [
|
||||
"dom",
|
||||
"esnext"
|
||||
],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"noEmitOnError": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"outDir": "lib",
|
||||
"pretty": true,
|
||||
"removeComments": false,
|
||||
"strict": true,
|
||||
"target": "es2015",
|
||||
"useDefineForClassFields": false,
|
||||
"composite": true,
|
||||
"declarationMap": true,
|
||||
"rootDir": "src",
|
||||
"skipLibCheck": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["jest"],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"lib",
|
||||
"test"
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"types/**/*"
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "@apache-superset/core",
|
||||
"version": "0.0.1-rc2",
|
||||
"version": "0.0.1-rc5",
|
||||
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
|
||||
"sideEffects": false,
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"esm",
|
||||
"lib"
|
||||
],
|
||||
"author": "",
|
||||
@@ -19,14 +18,15 @@
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@types/react": "^17.0.83",
|
||||
"install": "^0.13.0",
|
||||
"npm": "^11.1.0"
|
||||
"npm": "^11.1.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": "^5.24.6",
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "babel src --out-dir lib --extensions \".ts,.tsx\"",
|
||||
"build": "babel src --out-dir lib --extensions \".ts,.tsx\" && tsc --emitDeclarationOnly",
|
||||
"type": "tsc --noEmit"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@@ -31,12 +31,32 @@ import { Contributions } from './contributions';
|
||||
/**
|
||||
* Represents a database column with its name and data type.
|
||||
*/
|
||||
export declare interface Column {
|
||||
/** The name of the column */
|
||||
export type Column = {
|
||||
/**
|
||||
* Label of the column
|
||||
*/
|
||||
name: string;
|
||||
/** The data type of the column (e.g., 'INTEGER', 'VARCHAR', 'TIMESTAMP') */
|
||||
|
||||
/**
|
||||
* Column name defined
|
||||
*/
|
||||
column_name: string;
|
||||
|
||||
/**
|
||||
* The data type of the column (e.g., 'INTEGER', 'VARCHAR', 'TIMESTAMP')
|
||||
*/
|
||||
type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic data type format
|
||||
*/
|
||||
type_generic: GenericDataType;
|
||||
|
||||
/**
|
||||
* True if the column is date format
|
||||
*/
|
||||
is_dttm: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a database table with its name and column definitions.
|
||||
@@ -76,6 +96,45 @@ export declare interface Database {
|
||||
schemas: Schema[];
|
||||
}
|
||||
|
||||
// Keep in sync with superset/errors.py
|
||||
export type ErrorLevel = 'info' | 'warning' | 'error';
|
||||
|
||||
/**
|
||||
* Superset error object structure.
|
||||
* Contains details about an error that occurred within Superset.
|
||||
*/
|
||||
export type SupersetError = {
|
||||
/**
|
||||
* Error types, see enum of SupersetErrorType in superset/errors.py
|
||||
*/
|
||||
error_type: string;
|
||||
|
||||
/**
|
||||
* Extra properties based on the error types
|
||||
*/
|
||||
extra: Record<string, any>;
|
||||
|
||||
/**
|
||||
* Level of the error type
|
||||
*/
|
||||
level: ErrorLevel;
|
||||
|
||||
/**
|
||||
* Detail description for the error
|
||||
*/
|
||||
message: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic data types, see enum of the same name in superset/utils/core.py.
|
||||
*/
|
||||
export enum GenericDataType {
|
||||
Numeric = 0,
|
||||
String = 1,
|
||||
Temporal = 2,
|
||||
Boolean = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a type which can release resources, such
|
||||
* as event listening or a timer.
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
* - Global APIs: Functions and events available across the entire SQL Lab interface
|
||||
*/
|
||||
|
||||
import { Event, Database } from './core';
|
||||
import { Event, Database, SupersetError, Column } from './core';
|
||||
|
||||
/**
|
||||
* Represents an SQL editor instance within a SQL Lab tab.
|
||||
@@ -111,6 +111,126 @@ export interface Tab {
|
||||
panels: Panel[];
|
||||
}
|
||||
|
||||
export enum CTASMethod {
|
||||
Table = 'TABLE',
|
||||
View = 'VIEW',
|
||||
}
|
||||
|
||||
export interface CTAS {
|
||||
/**
|
||||
* Create method for CTAS creation request.
|
||||
*/
|
||||
method: CTASMethod;
|
||||
|
||||
/**
|
||||
* Temporary table name for creation using a CTAS query.
|
||||
*/
|
||||
tempTable: string | null;
|
||||
}
|
||||
|
||||
export interface QueryContext {
|
||||
/**
|
||||
* Unique query ID on client side.
|
||||
*/
|
||||
clientId: string;
|
||||
|
||||
/**
|
||||
* Contains CTAS if the query requests table creation.
|
||||
*/
|
||||
ctas: CTAS | null;
|
||||
|
||||
/**
|
||||
* Requested row limit for the query.
|
||||
*/
|
||||
requestedLimit: number | null;
|
||||
|
||||
/**
|
||||
* True if the query execution result will be/was delivered asynchronously
|
||||
*/
|
||||
runAsync?: boolean;
|
||||
|
||||
/**
|
||||
* Start datetime for the query in a numerical timestamp
|
||||
*/
|
||||
startDttm: number;
|
||||
|
||||
/**
|
||||
* The tab instance associated with the request query
|
||||
*/
|
||||
tab: Tab;
|
||||
|
||||
/**
|
||||
* A key-value JSON associated with Jinja template variables
|
||||
*/
|
||||
templateParameters: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface QueryErrorResultContext extends QueryContext {
|
||||
/**
|
||||
* Finished datetime for the query in a numerical timestamp
|
||||
*/
|
||||
endDttm: number;
|
||||
|
||||
/**
|
||||
* Error message returned from DB engine
|
||||
*/
|
||||
errorMessage: string;
|
||||
|
||||
/**
|
||||
* Error details in a SupersetError structure
|
||||
*/
|
||||
errors: SupersetError[] | null;
|
||||
|
||||
/**
|
||||
* Executed SQL after parsing Jinja templates.
|
||||
*/
|
||||
executedSql: string | null;
|
||||
}
|
||||
|
||||
export interface QueryResultContext extends QueryContext {
|
||||
/**
|
||||
* Actual number of rows returned by the query.
|
||||
*/
|
||||
appliedLimit: number;
|
||||
|
||||
/**
|
||||
* Major factor that is determining the row limit of the query results.
|
||||
*/
|
||||
appliedLimitingFactor: string;
|
||||
|
||||
/**
|
||||
* Finished datetime for the query in a numerical timestamp.
|
||||
*/
|
||||
endDttm: number;
|
||||
|
||||
/**
|
||||
* Executed SQL after parsing Jinja templates.
|
||||
*/
|
||||
executedSql: string;
|
||||
|
||||
/**
|
||||
* Remote query id stored in backend.
|
||||
*/
|
||||
remoteId: number;
|
||||
|
||||
/**
|
||||
* Query result data and metadata.
|
||||
*/
|
||||
result: QueryResult;
|
||||
}
|
||||
|
||||
export interface QueryResult {
|
||||
/**
|
||||
* Column metadata associated with the query result.
|
||||
*/
|
||||
columns: Column[];
|
||||
|
||||
/**
|
||||
* Query result data.
|
||||
*/
|
||||
data: Record<string, any>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tab-scoped Events and Functions
|
||||
*
|
||||
@@ -240,30 +360,30 @@ export declare const onDidChangeTabTitle: Event<string>;
|
||||
|
||||
/**
|
||||
* Event fired when a query starts running in the current tab.
|
||||
* Provides the editor state at the time of query execution.
|
||||
* Provides the query request state at the time of query execution.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQueryRun.event((editor) => {
|
||||
* console.log('Query started on database:', editor.databaseId);
|
||||
* console.log('Query content:', editor.content);
|
||||
* onDidQueryRun.event((query) => {
|
||||
* console.log('Query started on database:', query.tab.editor.databaseId);
|
||||
* console.log('Query content:', query.tab.editor.content);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidQueryRun: Event<Editor>;
|
||||
export declare const onDidQueryRun: Event<QueryContext>;
|
||||
|
||||
/**
|
||||
* Event fired when a running query is stopped in the current tab.
|
||||
* Provides the editor state when the query was stopped.
|
||||
* Provides the query request state when the query was stopped.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQueryStop.event((editor) => {
|
||||
* console.log('Query stopped for database:', editor.databaseId);
|
||||
* onDidQueryStop.event((query) => {
|
||||
* console.log('Query stopped for database:', query.tab.editor.databaseId);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidQueryStop: Event<Editor>;
|
||||
export declare const onDidQueryStop: Event<QueryContext>;
|
||||
|
||||
/**
|
||||
* Event fired when a query fails in the current tab.
|
||||
@@ -273,12 +393,12 @@ export declare const onDidQueryStop: Event<Editor>;
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQueryFail.event((error) => {
|
||||
* console.error('Query failed:', error);
|
||||
* onDidQueryFail.event((result) => {
|
||||
* console.error('Query failed:', result.errorMessage);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidQueryFail: Event<string>;
|
||||
export declare const onDidQueryFail: Event<QueryErrorResultContext>;
|
||||
|
||||
/**
|
||||
* Event fired when a query succeeds in the current tab.
|
||||
@@ -288,12 +408,13 @@ export declare const onDidQueryFail: Event<string>;
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onDidQuerySuccess.event((result) => {
|
||||
* console.log('Query succeeded:', result);
|
||||
* onDidQuerySuccess.event((query) => {
|
||||
* console.log('Query succeeded:', query.result.data);
|
||||
* console.log('Query executed content:', query.executedSql);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidQuerySuccess: Event<string>;
|
||||
export declare const onDidQuerySuccess: Event<QueryResultContext>;
|
||||
|
||||
/**
|
||||
* Global Events and Functions
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "lib",
|
||||
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
|
||||
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
|
||||
"baseUrl": "../..",
|
||||
|
||||
// Directory Overrides: Parent config paths are relative to frontend root,
|
||||
// but packages need paths relative to their own directory
|
||||
"outDir": "lib",
|
||||
"strict": true,
|
||||
"rootDir": "src",
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true
|
||||
"declarationDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*.ts*"],
|
||||
"exclude": ["lib"]
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@types/react": "*",
|
||||
"lodash": "^4.17.21",
|
||||
"prop-types": "^15.8.1"
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { ReactNode } from 'react';
|
||||
import { css, GenericDataType, styled, t } from '@superset-ui/core';
|
||||
import { css, styled, t } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import {
|
||||
ClockCircleOutlined,
|
||||
QuestionOutlined,
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Popover, type PopoverProps } from '@superset-ui/core/components';
|
||||
import type ReactAce from 'react-ace';
|
||||
import {
|
||||
Popover,
|
||||
type PopoverProps,
|
||||
SQLEditor,
|
||||
} from '@superset-ui/core/components';
|
||||
import { CalculatorOutlined } from '@ant-design/icons';
|
||||
import { css, styled, useTheme, t } from '@superset-ui/core';
|
||||
|
||||
@@ -35,24 +37,10 @@ const StyledCalculatorIcon = styled(CalculatorOutlined)`
|
||||
|
||||
export const SQLPopover = (props: PopoverProps & { sqlExpression: string }) => {
|
||||
const theme = useTheme();
|
||||
const [AceEditor, setAceEditor] = useState<typeof ReactAce | null>(null);
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
import('react-ace'),
|
||||
import('ace-builds/src-min-noconflict/mode-sql'),
|
||||
]).then(([reactAceModule]) => {
|
||||
setAceEditor(() => reactAceModule.default);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!AceEditor) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
<AceEditor
|
||||
mode="sql"
|
||||
<SQLEditor
|
||||
value={props.sqlExpression}
|
||||
editorProps={{ $blockScrolling: true }}
|
||||
setOptions={{
|
||||
@@ -65,7 +53,6 @@ export const SQLPopover = (props: PopoverProps & { sqlExpression: string }) => {
|
||||
wrapEnabled
|
||||
style={{
|
||||
border: `1px solid ${theme.colorBorder}`,
|
||||
background: theme.colorPrimaryBg,
|
||||
maxWidth: theme.sizeUnit * 100,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -16,13 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
DTTM_ALIAS,
|
||||
GenericDataType,
|
||||
QueryColumn,
|
||||
QueryMode,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import { DTTM_ALIAS, QueryColumn, QueryMode, t } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { ColumnMeta, SortSeriesData, SortSeriesType } from './types';
|
||||
|
||||
export const DEFAULT_MAX_ROW = 100000;
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { DatasourceType, GenericDataType } from '@superset-ui/core';
|
||||
import { DatasourceType } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { Dataset } from './types';
|
||||
|
||||
export const TestDataset: Dataset = {
|
||||
|
||||
@@ -43,15 +43,17 @@ export const renameOperator: PostProcessingFactory<PostProcessingRename> = (
|
||||
|
||||
// remove or rename top level of column name(metric name) in the MultiIndex when
|
||||
// 1) at least 1 metric
|
||||
// 2) dimension exist or multiple time shift metrics exist
|
||||
// 3) xAxis exist
|
||||
// 4) truncate_metric in form_data and truncate_metric is true
|
||||
// 2) xAxis exist
|
||||
// 3a) isTimeComparisonValue
|
||||
// 3b-1) dimension exist or multiple time shift metrics exist
|
||||
// 3b-2) truncate_metric in form_data and truncate_metric is true
|
||||
if (
|
||||
metrics.length > 0 &&
|
||||
(columns.length > 0 || timeOffsets.length > 1) &&
|
||||
xAxisLabel &&
|
||||
truncate_metric !== undefined &&
|
||||
!!truncate_metric
|
||||
(isTimeComparisonValue ||
|
||||
((columns.length > 0 || timeOffsets.length > 1) &&
|
||||
truncate_metric !== undefined &&
|
||||
!!truncate_metric))
|
||||
) {
|
||||
const renamePairs: [string, string | null][] = [];
|
||||
if (
|
||||
|
||||
@@ -20,20 +20,32 @@ import { t } from '@superset-ui/core';
|
||||
import { ControlPanelSectionConfig } from '../types';
|
||||
|
||||
export const matrixifyEnableSection: ControlPanelSectionConfig = {
|
||||
label: t('Enable matrixify'),
|
||||
label: t('Matrixify'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[
|
||||
{
|
||||
name: 'matrixify_enabled',
|
||||
name: 'matrixify_enable_horizontal_layout',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Enable matrixify'),
|
||||
label: t('Enable horizontal layout (columns)'),
|
||||
description: t(
|
||||
'Create matrix columns by placing charts side-by-side',
|
||||
),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'matrixify_enable_vertical_layout',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Enable vertical layout (rows)'),
|
||||
description: t('Create matrix rows by stacking charts vertically'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
description: t(
|
||||
'Transform this chart into a matrix/grid of charts based on dimensions or metrics',
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -42,9 +54,11 @@ export const matrixifyEnableSection: ControlPanelSectionConfig = {
|
||||
};
|
||||
|
||||
export const matrixifySection: ControlPanelSectionConfig = {
|
||||
label: t('Matrixify'),
|
||||
label: t('Cell layout & styling'),
|
||||
expanded: false,
|
||||
visibility: ({ controls }) => controls?.matrixify_enabled?.value === true,
|
||||
visibility: ({ controls }) =>
|
||||
controls?.matrixify_enable_vertical_layout?.value === true ||
|
||||
controls?.matrixify_enable_horizontal_layout?.value === true,
|
||||
controlSetRows: [
|
||||
[
|
||||
{
|
||||
@@ -105,9 +119,10 @@ export const matrixifySection: ControlPanelSectionConfig = {
|
||||
};
|
||||
|
||||
export const matrixifyRowSection: ControlPanelSectionConfig = {
|
||||
label: t('Vertical layout'),
|
||||
label: t('Vertical layout (rows)'),
|
||||
expanded: false,
|
||||
visibility: ({ controls }) => controls?.matrixify_enabled?.value === true,
|
||||
visibility: ({ controls }) =>
|
||||
controls?.matrixify_enable_vertical_layout?.value === true,
|
||||
controlSetRows: [
|
||||
['matrixify_show_row_labels'],
|
||||
['matrixify_mode_rows'],
|
||||
@@ -118,13 +133,14 @@ export const matrixifyRowSection: ControlPanelSectionConfig = {
|
||||
['matrixify_topn_metric_rows'],
|
||||
['matrixify_topn_order_rows'],
|
||||
],
|
||||
tabOverride: 'data',
|
||||
tabOverride: 'matrixify',
|
||||
};
|
||||
|
||||
export const matrixifyColumnSection: ControlPanelSectionConfig = {
|
||||
label: t('Horizontal layout'),
|
||||
label: t('Horizontal layout (columns)'),
|
||||
expanded: false,
|
||||
visibility: ({ controls }) => controls?.matrixify_enabled?.value === true,
|
||||
visibility: ({ controls }) =>
|
||||
controls?.matrixify_enable_horizontal_layout?.value === true,
|
||||
controlSetRows: [
|
||||
['matrixify_show_column_headers'],
|
||||
['matrixify_mode_columns'],
|
||||
@@ -135,5 +151,5 @@ export const matrixifyColumnSection: ControlPanelSectionConfig = {
|
||||
['matrixify_topn_metric_columns'],
|
||||
['matrixify_topn_order_columns'],
|
||||
],
|
||||
tabOverride: 'data',
|
||||
tabOverride: 'matrixify',
|
||||
};
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
import {
|
||||
ContributionType,
|
||||
ensureIsArray,
|
||||
GenericDataType,
|
||||
getColumnLabel,
|
||||
getMetricLabel,
|
||||
QueryFormColumn,
|
||||
QueryFormMetric,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import {
|
||||
ControlPanelState,
|
||||
ControlState,
|
||||
|
||||
@@ -17,12 +17,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
GenericDataType,
|
||||
QueryColumn,
|
||||
t,
|
||||
validateNonEmpty,
|
||||
} from '@superset-ui/core';
|
||||
import { QueryColumn, t, validateNonEmpty } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import {
|
||||
ExtraControlProps,
|
||||
SharedControlConfig,
|
||||
|
||||
@@ -18,21 +18,49 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { t } from '@superset-ui/core';
|
||||
import { t, validateNonEmpty } from '@superset-ui/core';
|
||||
import { SharedControlConfig } from '../types';
|
||||
import { dndAdhocMetricControl } from './dndControls';
|
||||
import { defineSavedMetrics } from '../utils';
|
||||
|
||||
/**
|
||||
* Matrixify control definitions
|
||||
* Controls for transforming charts into matrix/grid layouts
|
||||
*/
|
||||
|
||||
// Utility function to check if matrixify controls should be visible
|
||||
const isMatrixifyVisible = (
|
||||
controls: any,
|
||||
axis: 'rows' | 'columns',
|
||||
mode?: 'metrics' | 'dimensions',
|
||||
selectionMode?: 'members' | 'topn',
|
||||
) => {
|
||||
const layoutControl = `matrixify_enable_${axis === 'rows' ? 'vertical' : 'horizontal'}_layout`;
|
||||
const modeControl = `matrixify_mode_${axis}`;
|
||||
const selectionModeControl = `matrixify_dimension_selection_mode_${axis}`;
|
||||
|
||||
const isLayoutEnabled = controls?.[layoutControl]?.value === true;
|
||||
|
||||
if (!isLayoutEnabled) return false;
|
||||
|
||||
if (mode) {
|
||||
const isModeMatch = controls?.[modeControl]?.value === mode;
|
||||
if (!isModeMatch) return false;
|
||||
|
||||
if (selectionMode && mode === 'dimensions') {
|
||||
return controls?.[selectionModeControl]?.value === selectionMode;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Initialize the controls object that will be populated dynamically
|
||||
const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
|
||||
// Dynamically add axis-specific controls (rows and columns)
|
||||
['columns', 'rows'].forEach(axisParam => {
|
||||
const axis = axisParam; // Capture the value in a local variable
|
||||
(['columns', 'rows'] as const).forEach(axisParam => {
|
||||
const axis: 'rows' | 'columns' = axisParam;
|
||||
|
||||
matrixifyControls[`matrixify_mode_${axis}`] = {
|
||||
type: 'RadioButtonControl',
|
||||
@@ -43,17 +71,18 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
['dimensions', t('Dimension members')],
|
||||
],
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) => isMatrixifyVisible(controls, axis),
|
||||
};
|
||||
|
||||
matrixifyControls[`matrixify_${axis}`] = {
|
||||
...dndAdhocMetricControl,
|
||||
label: t(`Metrics`),
|
||||
multi: true,
|
||||
validators: [], // Not required
|
||||
// description: t(`Select metrics for ${axis}`),
|
||||
validators: [], // No validation - rely on visibility
|
||||
renderTrigger: true,
|
||||
visibility: ({ controls }) =>
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'metrics',
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) => isMatrixifyVisible(controls, axis, 'metrics'),
|
||||
};
|
||||
|
||||
// Combined dimension and values control
|
||||
@@ -62,8 +91,9 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
label: t(`Dimension selection`),
|
||||
description: t(`Select dimension and values`),
|
||||
default: { dimension: '', values: [] },
|
||||
validators: [], // Not required
|
||||
validators: [], // No validation - rely on visibility
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
shouldMapStateToProps: (prevState, state) => {
|
||||
// Recalculate when any relevant form_data field changes
|
||||
const fieldsToCheck = [
|
||||
@@ -82,24 +112,40 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
const getValue = (key: string, defaultValue?: any) =>
|
||||
form_data?.[key] ?? controls?.[key]?.value ?? defaultValue;
|
||||
|
||||
const selectionMode = getValue(
|
||||
`matrixify_dimension_selection_mode_${axis}`,
|
||||
'members',
|
||||
);
|
||||
|
||||
const isVisible = isMatrixifyVisible(controls, axis, 'dimensions');
|
||||
|
||||
// Validate dimension is selected when visible
|
||||
const dimensionValidator = (value: any) => {
|
||||
if (!value?.dimension) {
|
||||
return t('Dimension is required');
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Additional validation for topN mode
|
||||
const validators = isVisible
|
||||
? [dimensionValidator, validateNonEmpty]
|
||||
: [];
|
||||
|
||||
return {
|
||||
datasource,
|
||||
selectionMode: getValue(
|
||||
`matrixify_dimension_selection_mode_${axis}`,
|
||||
'members',
|
||||
),
|
||||
selectionMode,
|
||||
topNMetric: getValue(`matrixify_topn_metric_${axis}`),
|
||||
topNValue: getValue(`matrixify_topn_value_${axis}`),
|
||||
topNOrder: getValue(`matrixify_topn_order_${axis}`),
|
||||
formData: form_data,
|
||||
validators,
|
||||
};
|
||||
},
|
||||
visibility: ({ controls }) =>
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions',
|
||||
isMatrixifyVisible(controls, axis, 'dimensions'),
|
||||
};
|
||||
|
||||
// Dimension picker for TopN mode (just dimension, no values)
|
||||
// NOTE: This is now handled by matrixify_dimension control, so hiding it
|
||||
matrixifyControls[`matrixify_topn_dimension_${axis}`] = {
|
||||
type: 'SelectControl',
|
||||
label: t('Dimension'),
|
||||
@@ -127,33 +173,67 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
['topn', t('Top n')],
|
||||
],
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions',
|
||||
isMatrixifyVisible(controls, axis, 'dimensions'),
|
||||
};
|
||||
|
||||
// TopN controls
|
||||
matrixifyControls[`matrixify_topn_value_${axis}`] = {
|
||||
type: 'TextControl',
|
||||
type: 'NumberControl',
|
||||
label: t(`Number of top values`),
|
||||
description: t(`How many top values to select`),
|
||||
default: 10,
|
||||
isInt: true,
|
||||
validators: [],
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions' &&
|
||||
controls?.[`matrixify_dimension_selection_mode_${axis}`]?.value ===
|
||||
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
|
||||
mapStateToProps: ({ controls }) => {
|
||||
const isVisible = isMatrixifyVisible(
|
||||
controls,
|
||||
axis,
|
||||
'dimensions',
|
||||
'topn',
|
||||
);
|
||||
|
||||
return {
|
||||
validators: isVisible ? [validateNonEmpty] : [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
matrixifyControls[`matrixify_topn_metric_${axis}`] = {
|
||||
...dndAdhocMetricControl,
|
||||
label: t(`Metric for ordering`),
|
||||
multi: false,
|
||||
validators: [], // Not required
|
||||
validators: [],
|
||||
description: t(`Metric to use for ordering Top N values`),
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions' &&
|
||||
controls?.[`matrixify_dimension_selection_mode_${axis}`]?.value ===
|
||||
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
|
||||
mapStateToProps: (state, controlState) => {
|
||||
const { controls, datasource } = state;
|
||||
const isVisible = isMatrixifyVisible(
|
||||
controls,
|
||||
axis,
|
||||
'dimensions',
|
||||
'topn',
|
||||
);
|
||||
|
||||
const originalProps =
|
||||
dndAdhocMetricControl.mapStateToProps?.(state, controlState) || {};
|
||||
|
||||
return {
|
||||
...originalProps,
|
||||
columns: datasource?.columns || [],
|
||||
savedMetrics: defineSavedMetrics(datasource),
|
||||
datasource,
|
||||
datasourceType: datasource?.type,
|
||||
validators: isVisible ? [validateNonEmpty] : [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
matrixifyControls[`matrixify_topn_order_${axis}`] = {
|
||||
@@ -164,10 +244,10 @@ const matrixifyControls: Record<string, SharedControlConfig<any>> = {};
|
||||
['asc', t('Ascending')],
|
||||
['desc', t('Descending')],
|
||||
],
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
controls?.[`matrixify_mode_${axis}`]?.value === 'dimensions' &&
|
||||
controls?.[`matrixify_dimension_selection_mode_${axis}`]?.value ===
|
||||
'topn',
|
||||
isMatrixifyVisible(controls, axis, 'dimensions', 'topn'),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -213,15 +293,22 @@ matrixifyControls.matrixify_charts_per_row = {
|
||||
!controls?.matrixify_fit_columns_dynamically?.value,
|
||||
};
|
||||
|
||||
// Main enable control
|
||||
matrixifyControls.matrixify_enabled = {
|
||||
matrixifyControls.matrixify_enable_vertical_layout = {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Enable matrixify'),
|
||||
description: t(
|
||||
'Transform this chart into a matrix/grid of charts based on dimensions or metrics',
|
||||
),
|
||||
label: t('Enable vertical layout (rows)'),
|
||||
description: t('Create matrix rows by stacking charts vertically'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
};
|
||||
|
||||
matrixifyControls.matrixify_enable_horizontal_layout = {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Enable horizontal layout (columns)'),
|
||||
description: t('Create matrix columns by placing charts side-by-side'),
|
||||
default: false,
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
};
|
||||
|
||||
// Cell title control for Matrixify
|
||||
@@ -234,8 +321,8 @@ matrixifyControls.matrixify_cell_title_template = {
|
||||
default: '',
|
||||
renderTrigger: true,
|
||||
visibility: ({ controls }) =>
|
||||
(controls?.matrixify_mode_rows?.value ||
|
||||
controls?.matrixify_mode_columns?.value) !== undefined,
|
||||
controls?.matrixify_enable_vertical_layout?.value === true ||
|
||||
controls?.matrixify_enable_horizontal_layout?.value === true,
|
||||
};
|
||||
|
||||
// Matrix display controls
|
||||
@@ -245,9 +332,9 @@ matrixifyControls.matrixify_show_row_labels = {
|
||||
description: t('Display labels for each row on the left side of the matrix'),
|
||||
default: true,
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
(controls?.matrixify_mode_rows?.value ||
|
||||
controls?.matrixify_mode_columns?.value) !== undefined,
|
||||
controls?.matrixify_enable_vertical_layout?.value === true,
|
||||
};
|
||||
|
||||
matrixifyControls.matrixify_show_column_headers = {
|
||||
@@ -256,9 +343,9 @@ matrixifyControls.matrixify_show_column_headers = {
|
||||
description: t('Display headers for each column at the top of the matrix'),
|
||||
default: true,
|
||||
renderTrigger: true,
|
||||
tabOverride: 'matrixify',
|
||||
visibility: ({ controls }) =>
|
||||
(controls?.matrixify_mode_rows?.value ||
|
||||
controls?.matrixify_mode_columns?.value) !== undefined,
|
||||
controls?.matrixify_enable_horizontal_layout?.value === true,
|
||||
};
|
||||
|
||||
export { matrixifyControls };
|
||||
|
||||
@@ -16,12 +16,9 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ensureIsArray, GenericDataType, ValueOf } from '@superset-ui/core';
|
||||
import {
|
||||
ControlPanelState,
|
||||
isDataset,
|
||||
isQueryResponse,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { ensureIsArray, ValueOf } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { ControlPanelState, isDataset, isQueryResponse } from '../types';
|
||||
|
||||
export function checkColumnType(
|
||||
columnName: string,
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { GenericDataType, QueryColumn, QueryResponse } from '@superset-ui/core';
|
||||
import { QueryColumn, QueryResponse } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { ColumnMeta, Dataset, isDataset, isQueryResponse } from '../types';
|
||||
|
||||
export function columnsByType(
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
GenericDataType,
|
||||
getColumnLabel,
|
||||
isPhysicalColumn,
|
||||
QueryFormColumn,
|
||||
} from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { checkColumnType, ControlStateMapping } from '..';
|
||||
|
||||
export function isSortable(controls: ControlStateMapping): boolean {
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@superset-ui/core/spec';
|
||||
import { GenericDataType } from '@superset-ui/core';
|
||||
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { ColumnOption, ColumnOptionProps } from '../../src';
|
||||
|
||||
jest.mock('@superset-ui/chart-controls/components/SQLPopover', () => ({
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
import { isValidElement } from 'react';
|
||||
import { render, screen } from '@superset-ui/core/spec';
|
||||
import '@testing-library/jest-dom';
|
||||
import { GenericDataType } from '@superset-ui/core';
|
||||
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { ColumnTypeLabel, ColumnTypeLabelProps } from '../../src';
|
||||
|
||||
describe('ColumnOption', () => {
|
||||
|
||||
@@ -160,6 +160,44 @@ test('should add renameOperator if exists derived metrics', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should add renameOperator if isTimeComparisonValue without columns', () => {
|
||||
[
|
||||
ComparisonType.Difference,
|
||||
ComparisonType.Ratio,
|
||||
ComparisonType.Percentage,
|
||||
].forEach(type => {
|
||||
expect(
|
||||
renameOperator(
|
||||
{
|
||||
...formData,
|
||||
...{
|
||||
comparison_type: type,
|
||||
time_compare: ['1 year ago'],
|
||||
},
|
||||
},
|
||||
{
|
||||
...queryObject,
|
||||
...{
|
||||
columns: [],
|
||||
metrics: ['sum(val)', 'avg(val2)'],
|
||||
},
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
operation: 'rename',
|
||||
options: {
|
||||
columns: {
|
||||
[`${type}__avg(val2)__avg(val2)__1 year ago`]:
|
||||
'avg(val2), 1 year ago',
|
||||
[`${type}__sum(val)__sum(val)__1 year ago`]: 'sum(val), 1 year ago',
|
||||
},
|
||||
inplace: true,
|
||||
level: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should add renameOperator if x_axis does not exist', () => {
|
||||
expect(
|
||||
renameOperator(
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { GenericDataType, testQueryResponse } from '@superset-ui/core';
|
||||
import { testQueryResponse } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { checkColumnType, TestDataset } from '../../src';
|
||||
|
||||
test('checkColumnType columns from a Dataset', () => {
|
||||
|
||||
@@ -16,11 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
DatasourceType,
|
||||
GenericDataType,
|
||||
testQueryResponse,
|
||||
} from '@superset-ui/core';
|
||||
import { DatasourceType, testQueryResponse } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { columnChoices } from '../../src';
|
||||
|
||||
describe('columnChoices()', () => {
|
||||
|
||||
@@ -16,11 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
GenericDataType,
|
||||
testQueryResponse,
|
||||
testQueryResults,
|
||||
} from '@superset-ui/core';
|
||||
import { testQueryResponse, testQueryResults } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import {
|
||||
Dataset,
|
||||
getTemporalColumns,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { ControlStateMapping } from '@superset-ui/chart-controls';
|
||||
import { GenericDataType } from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/api/core';
|
||||
import { isSortable } from '../../src/utils/isSortable';
|
||||
|
||||
const controls: ControlStateMapping = {
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"exclude": [
|
||||
"lib",
|
||||
"test"
|
||||
],
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"types/**/*",
|
||||
"../../types/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
// Path Resolution: Override baseUrl to maintain correct path mappings from parent config
|
||||
// (e.g., "@apache-superset/core" -> "./packages/superset-core/src")
|
||||
"baseUrl": "../..",
|
||||
|
||||
// Directory Overrides: Parent config paths are relative to frontend root,
|
||||
// but packages need paths relative to their own directory
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
"declarationDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../superset-ui-core"
|
||||
}
|
||||
{ "path": "../superset-core" },
|
||||
{ "path": "../superset-ui-core" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,14 +24,15 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@babel/runtime": "^7.28.2",
|
||||
"@fontsource/fira-code": "^5.2.6",
|
||||
"@fontsource/inter": "^5.2.6",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"ace-builds": "^1.43.1",
|
||||
"ag-grid-community": "^34.0.2",
|
||||
"ag-grid-react": "34.0.2",
|
||||
"ag-grid-community": "34.2.0",
|
||||
"ag-grid-react": "34.2.0",
|
||||
"brace": "^0.11.1",
|
||||
"classnames": "^2.2.5",
|
||||
"csstype": "^3.1.3",
|
||||
|
||||
@@ -276,10 +276,10 @@ export function generateMatrixifyGrid(
|
||||
|
||||
const cellFormData = generateCellFormData(
|
||||
formData,
|
||||
rowCount > 1 ? config.rows : null,
|
||||
colCount > 1 ? config.columns : null,
|
||||
rowCount > 1 ? row : null,
|
||||
colCount > 1 ? col : null,
|
||||
rowCount > 0 ? config.rows : null,
|
||||
colCount > 0 ? config.columns : null,
|
||||
rowCount > 0 ? row : null,
|
||||
colCount > 0 ? col : null,
|
||||
);
|
||||
|
||||
// Generate title using template if provided
|
||||
|
||||
@@ -74,7 +74,8 @@ test('should create single group when fitting columns dynamically', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
matrixify_fit_columns_dynamically: true,
|
||||
matrixify_charts_per_row: 3,
|
||||
matrixify_show_row_labels: true,
|
||||
@@ -123,7 +124,8 @@ test('should create multiple groups when not fitting columns dynamically', () =>
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
matrixify_fit_columns_dynamically: false,
|
||||
matrixify_charts_per_row: 3,
|
||||
matrixify_show_row_labels: true,
|
||||
@@ -158,7 +160,8 @@ test('should handle exact division of columns', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
matrixify_fit_columns_dynamically: false,
|
||||
matrixify_charts_per_row: 2,
|
||||
matrixify_show_row_labels: true,
|
||||
@@ -186,7 +189,8 @@ test('should handle case where charts_per_row exceeds total columns', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
matrixify_fit_columns_dynamically: false,
|
||||
matrixify_charts_per_row: 5,
|
||||
matrixify_show_row_labels: true,
|
||||
@@ -216,7 +220,8 @@ test('should show headers for each group when wrapping occurs', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
matrixify_fit_columns_dynamically: false,
|
||||
matrixify_charts_per_row: 2,
|
||||
matrixify_show_row_labels: true,
|
||||
@@ -250,7 +255,8 @@ test('should show headers only on first row when not wrapping', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
matrixify_fit_columns_dynamically: true, // No wrapping
|
||||
matrixify_show_row_labels: true,
|
||||
matrixify_show_column_headers: true,
|
||||
@@ -279,7 +285,8 @@ test('should hide headers when disabled', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
matrixify_show_row_labels: false,
|
||||
matrixify_show_column_headers: false,
|
||||
};
|
||||
@@ -306,7 +313,8 @@ test('should place cells correctly in wrapped layout', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
matrixify_fit_columns_dynamically: false,
|
||||
matrixify_charts_per_row: 2,
|
||||
matrixify_show_row_labels: true,
|
||||
@@ -336,7 +344,8 @@ test('should handle null grid gracefully', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
};
|
||||
|
||||
const { container } = renderWithTheme(
|
||||
@@ -357,7 +366,8 @@ test('should handle empty grid gracefully', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
};
|
||||
|
||||
const { container } = renderWithTheme(
|
||||
@@ -381,7 +391,8 @@ test('should use default values for missing configuration', () => {
|
||||
|
||||
const formData = {
|
||||
viz_type: 'test_chart',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_enable_horizontal_layout: true,
|
||||
// Missing optional configurations
|
||||
};
|
||||
|
||||
|
||||
@@ -128,9 +128,13 @@ function MatrixifyGridRenderer({
|
||||
[formData],
|
||||
);
|
||||
|
||||
// Determine layout parameters
|
||||
const showRowLabels = formData.matrixify_show_row_labels ?? true;
|
||||
const showColumnHeaders = formData.matrixify_show_column_headers ?? true;
|
||||
// Determine layout parameters - only show headers/labels if layout is enabled
|
||||
const showRowLabels =
|
||||
formData.matrixify_enable_vertical_layout === true &&
|
||||
(formData.matrixify_show_row_labels ?? true);
|
||||
const showColumnHeaders =
|
||||
formData.matrixify_enable_horizontal_layout === true &&
|
||||
(formData.matrixify_show_column_headers ?? true);
|
||||
const rowHeight = formData.matrixify_row_height || DEFAULT_ROW_HEIGHT;
|
||||
const fitColumnsDynamically =
|
||||
formData.matrixify_fit_columns_dynamically ?? true;
|
||||
|
||||
@@ -26,6 +26,7 @@ interface LookupTable {
|
||||
|
||||
export interface ExampleImage {
|
||||
url: string;
|
||||
urlDark?: string;
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
@@ -38,6 +39,7 @@ export interface ChartMetadataConfig {
|
||||
enableNoResults?: boolean;
|
||||
supportedAnnotationTypes?: string[];
|
||||
thumbnail: string;
|
||||
thumbnailDark?: string;
|
||||
useLegacyApi?: boolean;
|
||||
behaviors?: Behavior[];
|
||||
exampleGallery?: ExampleImage[];
|
||||
@@ -71,6 +73,8 @@ export default class ChartMetadata {
|
||||
|
||||
thumbnail: string;
|
||||
|
||||
thumbnailDark?: string;
|
||||
|
||||
useLegacyApi: boolean;
|
||||
|
||||
behaviors: Behavior[];
|
||||
@@ -107,6 +111,7 @@ export default class ChartMetadata {
|
||||
description = '',
|
||||
supportedAnnotationTypes = [],
|
||||
thumbnail,
|
||||
thumbnailDark,
|
||||
useLegacyApi = false,
|
||||
behaviors = [],
|
||||
datasourceCount = 1,
|
||||
@@ -138,6 +143,7 @@ export default class ChartMetadata {
|
||||
);
|
||||
this.supportedAnnotationTypes = supportedAnnotationTypes;
|
||||
this.thumbnail = thumbnail;
|
||||
this.thumbnailDark = thumbnailDark;
|
||||
this.useLegacyApi = useLegacyApi;
|
||||
this.behaviors = behaviors;
|
||||
this.datasourceCount = datasourceCount;
|
||||
|
||||
@@ -37,10 +37,11 @@ test('isMatrixifyEnabled should return false when no matrixify configuration exi
|
||||
expect(isMatrixifyEnabled(formData)).toBe(false);
|
||||
});
|
||||
|
||||
test('isMatrixifyEnabled should return false when matrixify_enabled is false', () => {
|
||||
test('isMatrixifyEnabled should return false when layout controls are false', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: false,
|
||||
matrixify_enable_vertical_layout: false,
|
||||
matrixify_enable_horizontal_layout: false,
|
||||
matrixify_mode_rows: 'metrics',
|
||||
matrixify_rows: [createMetric('Revenue')],
|
||||
} as MatrixifyFormData;
|
||||
@@ -51,7 +52,7 @@ test('isMatrixifyEnabled should return false when matrixify_enabled is false', (
|
||||
test('isMatrixifyEnabled should return true for valid metrics mode configuration', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'metrics',
|
||||
matrixify_mode_columns: 'metrics',
|
||||
matrixify_rows: [createMetric('Revenue')],
|
||||
@@ -64,7 +65,7 @@ test('isMatrixifyEnabled should return true for valid metrics mode configuration
|
||||
test('isMatrixifyEnabled should return true for valid dimensions mode configuration', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_mode_columns: 'dimensions',
|
||||
matrixify_dimension_rows: { dimension: 'country', values: ['USA'] },
|
||||
@@ -77,7 +78,7 @@ test('isMatrixifyEnabled should return true for valid dimensions mode configurat
|
||||
test('isMatrixifyEnabled should return true for mixed mode configuration', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'metrics',
|
||||
matrixify_mode_columns: 'dimensions',
|
||||
matrixify_rows: [createMetric('Revenue')],
|
||||
@@ -90,7 +91,7 @@ test('isMatrixifyEnabled should return true for mixed mode configuration', () =>
|
||||
test('isMatrixifyEnabled should return true for topn dimension selection mode', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_mode_columns: 'dimensions',
|
||||
matrixify_dimension_rows: {
|
||||
@@ -109,7 +110,7 @@ test('isMatrixifyEnabled should return true for topn dimension selection mode',
|
||||
test('isMatrixifyEnabled should return false when both axes have empty metrics arrays', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'metrics',
|
||||
matrixify_mode_columns: 'metrics',
|
||||
matrixify_rows: [],
|
||||
@@ -122,7 +123,7 @@ test('isMatrixifyEnabled should return false when both axes have empty metrics a
|
||||
test('isMatrixifyEnabled should return false when both dimensions have empty values and no topn mode', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_mode_columns: 'dimensions',
|
||||
matrixify_dimension_rows: { dimension: 'country', values: [] },
|
||||
@@ -140,7 +141,7 @@ test('getMatrixifyConfig should return null when no matrixify configuration exis
|
||||
test('getMatrixifyConfig should return valid config for metrics mode', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'metrics',
|
||||
matrixify_mode_columns: 'metrics',
|
||||
matrixify_rows: [createMetric('Revenue')],
|
||||
@@ -158,7 +159,7 @@ test('getMatrixifyConfig should return valid config for metrics mode', () => {
|
||||
test('getMatrixifyConfig should return valid config for dimensions mode', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_mode_columns: 'dimensions',
|
||||
matrixify_dimension_rows: { dimension: 'country', values: ['USA'] },
|
||||
@@ -182,7 +183,7 @@ test('getMatrixifyConfig should return valid config for dimensions mode', () =>
|
||||
test('getMatrixifyConfig should handle topn selection mode', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_mode_columns: 'dimensions',
|
||||
matrixify_dimension_rows: {
|
||||
@@ -203,7 +204,8 @@ test('getMatrixifyConfig should handle topn selection mode', () => {
|
||||
test('getMatrixifyValidationErrors should return empty array when matrixify is not enabled', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: false,
|
||||
matrixify_enable_vertical_layout: false,
|
||||
matrixify_enable_horizontal_layout: false,
|
||||
} as MatrixifyFormData;
|
||||
|
||||
expect(getMatrixifyValidationErrors(formData)).toEqual([]);
|
||||
@@ -212,7 +214,7 @@ test('getMatrixifyValidationErrors should return empty array when matrixify is n
|
||||
test('getMatrixifyValidationErrors should return empty array when properly configured', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'metrics',
|
||||
matrixify_mode_columns: 'metrics',
|
||||
matrixify_rows: [createMetric('Revenue')],
|
||||
@@ -225,7 +227,7 @@ test('getMatrixifyValidationErrors should return empty array when properly confi
|
||||
test('getMatrixifyValidationErrors should return error when enabled but no configuration exists', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
} as MatrixifyFormData;
|
||||
|
||||
const errors = getMatrixifyValidationErrors(formData);
|
||||
@@ -235,7 +237,7 @@ test('getMatrixifyValidationErrors should return error when enabled but no confi
|
||||
test('getMatrixifyValidationErrors should return error when metrics mode has no metrics', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'metrics',
|
||||
matrixify_rows: [],
|
||||
matrixify_columns: [],
|
||||
@@ -261,7 +263,7 @@ test('should handle empty form data object', () => {
|
||||
test('should handle partial configuration with one axis only', () => {
|
||||
const formData = {
|
||||
viz_type: 'table',
|
||||
matrixify_enabled: true,
|
||||
matrixify_enable_vertical_layout: true,
|
||||
matrixify_mode_rows: 'metrics',
|
||||
matrixify_rows: [createMetric('Revenue')],
|
||||
// No columns configuration
|
||||
|
||||
@@ -96,8 +96,9 @@ export interface MatrixifyAxisConfig {
|
||||
* Complete Matrixify configuration in form data
|
||||
*/
|
||||
export interface MatrixifyFormData {
|
||||
// Enable/disable matrixify functionality
|
||||
matrixify_enabled?: boolean;
|
||||
// Layout enable controls
|
||||
matrixify_enable_vertical_layout?: boolean;
|
||||
matrixify_enable_horizontal_layout?: boolean;
|
||||
|
||||
// Row axis configuration
|
||||
matrixify_mode_rows?: MatrixifyMode;
|
||||
@@ -177,8 +178,12 @@ export function getMatrixifyConfig(
|
||||
* Check if Matrixify is enabled and properly configured
|
||||
*/
|
||||
export function isMatrixifyEnabled(formData: MatrixifyFormData): boolean {
|
||||
// First check if matrixify is explicitly enabled via checkbox
|
||||
if (!formData.matrixify_enabled) {
|
||||
// Check if either vertical or horizontal layout is enabled
|
||||
const hasVerticalLayout = formData.matrixify_enable_vertical_layout === true;
|
||||
const hasHorizontalLayout =
|
||||
formData.matrixify_enable_horizontal_layout === true;
|
||||
|
||||
if (!hasVerticalLayout && !hasHorizontalLayout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -216,7 +221,11 @@ export function getMatrixifyValidationErrors(
|
||||
const errors: string[] = [];
|
||||
|
||||
// Only validate if matrixify is enabled
|
||||
if (!formData.matrixify_enabled) {
|
||||
const hasVerticalLayout = formData.matrixify_enable_vertical_layout === true;
|
||||
const hasHorizontalLayout =
|
||||
formData.matrixify_enable_horizontal_layout === true;
|
||||
|
||||
if (!hasVerticalLayout && !hasHorizontalLayout) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,21 +123,33 @@ export function AsyncAceEditor(
|
||||
const cssWorkerUrlPromise = import(
|
||||
'ace-builds/src-min-noconflict/worker-css'
|
||||
);
|
||||
const javascriptWorkerUrlPromise = import(
|
||||
'ace-builds/src-min-noconflict/worker-javascript'
|
||||
);
|
||||
const htmlWorkerUrlPromise = import(
|
||||
'ace-builds/src-min-noconflict/worker-html'
|
||||
);
|
||||
const acequirePromise = import('ace-builds/src-min-noconflict/ace');
|
||||
|
||||
const [
|
||||
{ default: ReactAceEditor },
|
||||
{ config },
|
||||
{ default: cssWorkerUrl },
|
||||
{ default: javascriptWorkerUrl },
|
||||
{ default: htmlWorkerUrl },
|
||||
{ require: acequire },
|
||||
] = await Promise.all([
|
||||
reactAcePromise,
|
||||
aceBuildsConfigPromise,
|
||||
cssWorkerUrlPromise,
|
||||
javascriptWorkerUrlPromise,
|
||||
htmlWorkerUrlPromise,
|
||||
acequirePromise,
|
||||
]);
|
||||
|
||||
config.setModuleUrl('ace/mode/css_worker', cssWorkerUrl);
|
||||
config.setModuleUrl('ace/mode/javascript_worker', javascriptWorkerUrl);
|
||||
config.setModuleUrl('ace/mode/html_worker', htmlWorkerUrl);
|
||||
|
||||
await Promise.all(aceModules.map(x => aceModuleLoaders[x]()));
|
||||
|
||||
@@ -393,10 +405,7 @@ export const FullSQLEditor = AsyncAceEditor(
|
||||
},
|
||||
);
|
||||
|
||||
export const MarkdownEditor = AsyncAceEditor([
|
||||
'mode/markdown',
|
||||
'theme/textmate',
|
||||
]);
|
||||
export const MarkdownEditor = AsyncAceEditor(['mode/markdown', 'theme/github']);
|
||||
|
||||
export const TextAreaEditor = AsyncAceEditor([
|
||||
'mode/markdown',
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/* eslint-disable import/first */
|
||||
/**
|
||||
* 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 { FC } from 'react';
|
||||
import AceEditor, { IAceEditorProps } from 'react-ace';
|
||||
import ace from 'ace-builds/src-noconflict/ace';
|
||||
|
||||
// Disable workers to avoid localhost loading issues
|
||||
ace.config.set('useWorker', false);
|
||||
|
||||
// Import required modes and themes after ace is loaded
|
||||
import 'ace-builds/src-min-noconflict/mode-handlebars';
|
||||
import 'ace-builds/src-min-noconflict/mode-css';
|
||||
import 'ace-builds/src-min-noconflict/mode-json';
|
||||
import 'ace-builds/src-min-noconflict/mode-sql';
|
||||
import 'ace-builds/src-min-noconflict/mode-markdown';
|
||||
import 'ace-builds/src-min-noconflict/mode-javascript';
|
||||
import 'ace-builds/src-min-noconflict/mode-html';
|
||||
import 'ace-builds/src-noconflict/theme-github';
|
||||
import 'ace-builds/src-noconflict/theme-monokai';
|
||||
|
||||
export type CodeEditorMode =
|
||||
| 'handlebars'
|
||||
| 'css'
|
||||
| 'json'
|
||||
| 'sql'
|
||||
| 'markdown'
|
||||
| 'javascript'
|
||||
| 'html';
|
||||
|
||||
export type CodeEditorTheme = 'light' | 'dark';
|
||||
|
||||
export interface CodeEditorProps
|
||||
extends Omit<IAceEditorProps, 'mode' | 'theme'> {
|
||||
mode?: CodeEditorMode;
|
||||
theme?: CodeEditorTheme;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export const CodeEditor: FC<CodeEditorProps> = ({
|
||||
mode = 'handlebars',
|
||||
theme = 'dark',
|
||||
name,
|
||||
width = '100%',
|
||||
height = '300px',
|
||||
value,
|
||||
fontSize = 14,
|
||||
showPrintMargin = true,
|
||||
focus = true,
|
||||
wrapEnabled = true,
|
||||
highlightActiveLine = true,
|
||||
editorProps = { $blockScrolling: true },
|
||||
setOptions,
|
||||
...rest
|
||||
}: CodeEditorProps) => {
|
||||
const editorName = name || Math.random().toString(36).substring(7);
|
||||
const aceTheme = theme === 'light' ? 'github' : 'monokai';
|
||||
|
||||
return (
|
||||
<AceEditor
|
||||
mode={mode}
|
||||
theme={aceTheme}
|
||||
name={editorName}
|
||||
height={height}
|
||||
width={width}
|
||||
value={value}
|
||||
fontSize={fontSize}
|
||||
showPrintMargin={showPrintMargin}
|
||||
focus={focus}
|
||||
editorProps={editorProps}
|
||||
wrapEnabled={wrapEnabled}
|
||||
highlightActiveLine={highlightActiveLine}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
showLineNumbers: true,
|
||||
tabSize: 2,
|
||||
showGutter: true,
|
||||
fontFamily:
|
||||
'Menlo, Consolas, Courier New, Ubuntu Mono, source-code-pro, Lucida Console, monospace',
|
||||
...setOptions,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeEditor;
|
||||
@@ -60,7 +60,7 @@ const EmptyStateContainer = styled.div`
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: ${theme.colorTextQuaternary};
|
||||
color: ${theme.colorTextTertiary};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: ${theme.sizeUnit * 4}px;
|
||||
@@ -84,7 +84,7 @@ const EmptyStateContainer = styled.div`
|
||||
const Title = styled.p<{ size: EmptyStateSize }>`
|
||||
${({ theme, size }) => css`
|
||||
font-size: ${size === 'large' ? theme.fontSizeLG : theme.fontSize}px;
|
||||
color: ${theme.colorTextQuaternary};
|
||||
color: ${theme.colorTextTertiary};
|
||||
margin-top: ${size === 'large' ? theme.sizeUnit * 4 : theme.sizeUnit * 2}px;
|
||||
font-weight: ${theme.fontWeightStrong};
|
||||
`}
|
||||
@@ -93,7 +93,7 @@ const Title = styled.p<{ size: EmptyStateSize }>`
|
||||
const Description = styled.p<{ size: EmptyStateSize }>`
|
||||
${({ theme, size }) => css`
|
||||
font-size: ${size === 'large' ? theme.fontSize : theme.fontSizeSM}px;
|
||||
color: ${theme.colorTextQuaternary};
|
||||
color: ${theme.colorTextTertiary};
|
||||
margin-top: ${theme.sizeUnit * 2}px;
|
||||
`}
|
||||
`;
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
import { useEffect, useState, FunctionComponent } from 'react';
|
||||
|
||||
import { t, styled, css, useTheme } from '@superset-ui/core';
|
||||
import dayjs from 'dayjs';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import { extendedDayjs } from '../../utils/dates';
|
||||
import 'dayjs/plugin/updateLocale';
|
||||
import 'dayjs/plugin/calendar';
|
||||
import { Icons } from '../Icons';
|
||||
import type { LastUpdatedProps } from './types';
|
||||
|
||||
@@ -46,9 +48,7 @@ export const LastUpdated: FunctionComponent<LastUpdatedProps> = ({
|
||||
update,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [timeSince, setTimeSince] = useState<dayjs.Dayjs>(
|
||||
extendedDayjs(updatedAt),
|
||||
);
|
||||
const [timeSince, setTimeSince] = useState<Dayjs>(extendedDayjs(updatedAt));
|
||||
|
||||
useEffect(() => {
|
||||
setTimeSince(() => extendedDayjs(updatedAt));
|
||||
|
||||
@@ -1,37 +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 { render, screen, userEvent } from '@superset-ui/core/spec';
|
||||
import { Ellipsis } from './Ellipsis';
|
||||
|
||||
test('Ellipsis - click when the button is enabled', async () => {
|
||||
const click = jest.fn();
|
||||
render(<Ellipsis onClick={click} />);
|
||||
expect(click).toHaveBeenCalledTimes(0);
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
expect(click).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Ellipsis - click when the button is disabled', async () => {
|
||||
const click = jest.fn();
|
||||
render(<Ellipsis onClick={click} disabled />);
|
||||
expect(click).toHaveBeenCalledTimes(0);
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
expect(click).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
@@ -1,47 +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 { render, screen, userEvent } from '@superset-ui/core/spec';
|
||||
import { Item } from './Item';
|
||||
|
||||
test('Item - click when the item is not active', async () => {
|
||||
const click = jest.fn();
|
||||
render(
|
||||
<Item onClick={click}>
|
||||
<div data-test="test" />
|
||||
</Item>,
|
||||
);
|
||||
expect(click).toHaveBeenCalledTimes(0);
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
expect(click).toHaveBeenCalledTimes(1);
|
||||
expect(screen.getByTestId('test')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Item - click when the item is active', async () => {
|
||||
const click = jest.fn();
|
||||
render(
|
||||
<Item onClick={click} active>
|
||||
<div data-test="test" />
|
||||
</Item>,
|
||||
);
|
||||
expect(click).toHaveBeenCalledTimes(0);
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
expect(click).toHaveBeenCalledTimes(0);
|
||||
expect(screen.getByTestId('test')).toBeInTheDocument();
|
||||
});
|
||||
@@ -1,45 +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 { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { PaginationButtonProps } from './types';
|
||||
|
||||
interface PaginationItemButton extends PaginationButtonProps {
|
||||
active?: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function Item({ active, children, onClick }: PaginationItemButton) {
|
||||
return (
|
||||
<li className={classNames({ active })}>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
if (!active) onClick(e);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -1,75 +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 { render, screen, cleanup } from '@superset-ui/core/spec';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
// Add cleanup after each test
|
||||
afterEach(async () => {
|
||||
cleanup();
|
||||
// Wait for any pending effects to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
jest.mock('./Next', () => ({
|
||||
Next: () => <div data-test="next" />,
|
||||
}));
|
||||
jest.mock('./Prev', () => ({
|
||||
Prev: () => <div data-test="prev" />,
|
||||
}));
|
||||
jest.mock('./Item', () => ({
|
||||
Item: () => <div data-test="item" />,
|
||||
}));
|
||||
jest.mock('./Ellipsis', () => ({
|
||||
Ellipsis: () => <div data-test="ellipsis" />,
|
||||
}));
|
||||
|
||||
test('Pagination rendering correctly', async () => {
|
||||
render(
|
||||
<Wrapper>
|
||||
<li data-test="test" />
|
||||
</Wrapper>,
|
||||
);
|
||||
expect(screen.getByRole('navigation')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('test')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Next attribute', async () => {
|
||||
render(<Wrapper.Next onClick={jest.fn()} />);
|
||||
expect(screen.getByTestId('next')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Prev attribute', async () => {
|
||||
render(<Wrapper.Next onClick={jest.fn()} />);
|
||||
expect(screen.getByTestId('next')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Item attribute', async () => {
|
||||
render(
|
||||
<Wrapper.Item onClick={jest.fn()}>
|
||||
<></>
|
||||
</Wrapper.Item>,
|
||||
);
|
||||
expect(screen.getByTestId('item')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Ellipsis attribute', async () => {
|
||||
render(<Wrapper.Ellipsis onClick={jest.fn()} />);
|
||||
expect(screen.getByTestId('ellipsis')).toBeInTheDocument();
|
||||
});
|
||||
@@ -1,90 +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 { styled } from '@superset-ui/core';
|
||||
import { Next } from './Next';
|
||||
import { Prev } from './Prev';
|
||||
import { Item } from './Item';
|
||||
import { Ellipsis } from './Ellipsis';
|
||||
|
||||
interface PaginationProps {
|
||||
children: JSX.Element | JSX.Element[];
|
||||
}
|
||||
|
||||
const PaginationList = styled.ul`
|
||||
${({ theme }) => `
|
||||
display: inline-block;
|
||||
padding: ${theme.sizeUnit * 3}px;
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
margin: 0 4px;
|
||||
|
||||
> span {
|
||||
padding: 8px 12px;
|
||||
text-decoration: none;
|
||||
background-color: ${theme.colorBgContainer};
|
||||
border: 1px solid ${theme.colorBorder};
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
color: ${theme.colorText};
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
z-index: 2;
|
||||
color: ${theme.colorText};
|
||||
background-color: ${theme.colorBgLayout};
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
span {
|
||||
background-color: transparent;
|
||||
cursor: default;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
span {
|
||||
z-index: 3;
|
||||
color: ${theme.colorBgLayout};
|
||||
cursor: default;
|
||||
background-color: ${theme.colorPrimary};
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
function Pagination({ children }: PaginationProps) {
|
||||
return <PaginationList role="navigation">{children}</PaginationList>;
|
||||
}
|
||||
|
||||
Pagination.Next = Next;
|
||||
Pagination.Prev = Prev;
|
||||
Pagination.Item = Item;
|
||||
Pagination.Ellipsis = Ellipsis;
|
||||
|
||||
export default Pagination;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user