mirror of
https://github.com/apache/superset.git
synced 2026-06-12 19:19:20 +00:00
Compare commits
43 Commits
custom-dri
...
msyavuz/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f41b6b699 | ||
|
|
ecb3ac68ff | ||
|
|
076e477fd4 | ||
|
|
1e4bc6ee78 | ||
|
|
db178cf527 | ||
|
|
5901320933 | ||
|
|
23bb4f88c0 | ||
|
|
4130b92966 | ||
|
|
38297edc6b | ||
|
|
0c8f326258 | ||
|
|
127f6b3d66 | ||
|
|
7609c33745 | ||
|
|
cd16218fbf | ||
|
|
bed45e42ac | ||
|
|
8286a1f2a5 | ||
|
|
ea519a77b5 | ||
|
|
6cb3ef9f5d | ||
|
|
a889ae75fc | ||
|
|
b60be9655f | ||
|
|
f570786f44 | ||
|
|
f9b399328d | ||
|
|
7e222d54b6 | ||
|
|
f3da8510d0 | ||
|
|
0dc2a02d2e | ||
|
|
10055ed4c7 | ||
|
|
e9a2fa6c63 | ||
|
|
3e491be312 | ||
|
|
d1ee1307ff | ||
|
|
a3d28f6615 | ||
|
|
f95efae874 | ||
|
|
14e0d220e7 | ||
|
|
f802e3a454 | ||
|
|
58e493d471 | ||
|
|
c453757c48 | ||
|
|
e416fece27 | ||
|
|
6b65ab7a29 | ||
|
|
1f1b0389ce | ||
|
|
519835e1a4 | ||
|
|
8cbb61dd7e | ||
|
|
960a31f211 | ||
|
|
a1242bd80e | ||
|
|
291e07c345 | ||
|
|
7c745ac622 |
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/`*
|
||||
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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -160,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
|
||||
@@ -266,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
|
||||
|
||||
@@ -331,6 +331,7 @@ greenlet==3.1.1
|
||||
# apache-superset
|
||||
# gevent
|
||||
# shillelagh
|
||||
# sqlalchemy
|
||||
grpcio==1.71.0
|
||||
# via
|
||||
# apache-superset
|
||||
@@ -536,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
|
||||
|
||||
@@ -1,193 +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').then(() => {
|
||||
cy.get('.slice_container').should('be.visible');
|
||||
});
|
||||
|
||||
cy.verifySliceSuccess({ waitAlias: '@tableChartData' });
|
||||
|
||||
cy.contains('View all charts').should('be.visible').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="Columns and metrics"]', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.click();
|
||||
cy.get('input[aria-label="Columns and metrics"]').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'],
|
||||
|
||||
36
superset-frontend/package-lock.json
generated
36
superset-frontend/package-lock.json
generated
@@ -54,6 +54,8 @@
|
||||
"@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",
|
||||
@@ -18713,27 +18715,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": {
|
||||
@@ -60688,7 +60690,7 @@
|
||||
},
|
||||
"packages/superset-core": {
|
||||
"name": "@apache-superset/core",
|
||||
"version": "0.0.1-rc3",
|
||||
"version": "0.0.1-rc4",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.26.4",
|
||||
@@ -63385,6 +63387,7 @@
|
||||
"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"
|
||||
@@ -63412,14 +63415,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",
|
||||
@@ -65458,6 +65462,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
@@ -65509,6 +65514,7 @@
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"echarts": "*",
|
||||
@@ -66686,6 +66692,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.17.11",
|
||||
@@ -67817,6 +67824,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
|
||||
@@ -127,6 +127,8 @@
|
||||
"@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",
|
||||
|
||||
@@ -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,19 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"strict": true,
|
||||
"rootDir": "src",
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"target": "es2020",
|
||||
"esModuleInterop": true
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*.ts*"],
|
||||
"exclude": ["lib"]
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@types/react": "*",
|
||||
"lodash": "^4.17.21"
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -17,11 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { ensureIsArray, GenericDataType, ValueOf } from '@superset-ui/core';
|
||||
import {
|
||||
ControlPanelState,
|
||||
isDataset,
|
||||
isQueryResponse,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { ControlPanelState, isDataset, isQueryResponse } from '../types';
|
||||
|
||||
export function checkColumnType(
|
||||
columnName: string,
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"exclude": [
|
||||
"lib",
|
||||
"test"
|
||||
],
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"types/**/*",
|
||||
"../../types/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../..",
|
||||
"outDir": "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",
|
||||
|
||||
@@ -204,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([]);
|
||||
|
||||
@@ -96,9 +96,6 @@ 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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -43,6 +43,7 @@ dayjs.updateLocale('en', {
|
||||
});
|
||||
|
||||
export const extendedDayjs = dayjs;
|
||||
export type { Dayjs };
|
||||
|
||||
export const fDuration = function (
|
||||
t1: number,
|
||||
|
||||
@@ -2,14 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"src/*": ["./src/*"],
|
||||
"@superset-ui/core": ["src"],
|
||||
"@superset-ui/core/*": ["src/*"]
|
||||
}
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"exclude": [
|
||||
"lib",
|
||||
"test"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"spec/**/*",
|
||||
"types/**/*"
|
||||
],
|
||||
"references": []
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"],
|
||||
"references": [{ "path": "../superset-core" }]
|
||||
}
|
||||
|
||||
@@ -19,3 +19,5 @@
|
||||
declare module '*.gif';
|
||||
declare module '*.svg';
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
declare module '*.jpeg';
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"exclude": [
|
||||
"lib",
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"types/**/*",
|
||||
"../../types/**/*"
|
||||
],
|
||||
"references": []
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"]
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -17,11 +17,14 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export default function roundDecimal(number, precision) {
|
||||
let roundedNumber;
|
||||
let p = precision;
|
||||
export default function roundDecimal(
|
||||
number: number,
|
||||
precision?: number,
|
||||
): number {
|
||||
let roundedNumber: number;
|
||||
if (precision) {
|
||||
roundedNumber = Math.round(number * (p = 10 ** p)) / p;
|
||||
const p = 10 ** precision;
|
||||
roundedNumber = Math.round(number * p) / p;
|
||||
} else {
|
||||
roundedNumber = Math.round(number);
|
||||
}
|
||||
@@ -2,18 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"d3v3": ["./types/d3v3"]
|
||||
}
|
||||
|
||||
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@superset-ui/core/components": ["../../packages/superset-ui-core/src/components"]
|
||||
}
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
*/
|
||||
import { useEffect, useState, memo } from 'react';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
|
||||
import { SafeMarkdown } from '@superset-ui/core/components';
|
||||
import Handlebars from 'handlebars';
|
||||
import dayjs from 'dayjs';
|
||||
import { isPlainObject } from 'lodash';
|
||||
|
||||
export interface HandlebarsRendererProps {
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@superset-ui/core/components": ["../../packages/superset-ui-core/src/components"]
|
||||
}
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
*/
|
||||
import { kebabCase, throttle } from 'lodash';
|
||||
import d3 from 'd3';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import nv from 'nvd3-fork';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -34,6 +33,7 @@ import {
|
||||
t,
|
||||
VizType,
|
||||
} from '@superset-ui/core';
|
||||
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
|
||||
|
||||
import 'nvd3-fork/build/nv.d3.css';
|
||||
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"xss": "^1.0.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
|
||||
@@ -30,7 +30,7 @@ export const useIsDark = () => {
|
||||
return tinycolor(theme.colorBgContainer).isDark();
|
||||
};
|
||||
|
||||
const useTableTheme = () => {
|
||||
const useTableTheme = (): ReturnType<typeof themeQuartz.withPart> => {
|
||||
const baseTheme = themeQuartz;
|
||||
const isDarkTheme = useIsDark();
|
||||
const tableTheme = isDarkTheme
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"exclude": ["lib", "test"],
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*", "types/**/*", "../../types/**/*"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/superset-ui-chart-controls"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/superset-ui-core"
|
||||
}
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,21 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../packages/superset-ui-chart-controls"
|
||||
},
|
||||
{
|
||||
"path": "../../../packages/superset-ui-core"
|
||||
},
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"echarts": "*",
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { Metric } from '@superset-ui/chart-controls';
|
||||
import {
|
||||
ChartProps,
|
||||
@@ -27,6 +25,8 @@ import {
|
||||
SimpleAdhocFilter,
|
||||
ensureIsArray,
|
||||
} from '@superset-ui/core';
|
||||
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
|
||||
import 'dayjs/plugin/utc';
|
||||
import {
|
||||
getComparisonFontSize,
|
||||
getHeaderFontSize,
|
||||
@@ -35,8 +35,6 @@ import {
|
||||
|
||||
import { getOriginalLabel } from '../utils';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
export const parseMetricValue = (metricValue: number | string | null) => {
|
||||
if (typeof metricValue === 'string') {
|
||||
const dateObject = dayjs.utc(metricValue, undefined, true);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import {
|
||||
getTimeFormatter,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
SMART_DATE_ID,
|
||||
TimeGranularity,
|
||||
} from '@superset-ui/core';
|
||||
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import { t } from '@superset-ui/core';
|
||||
import {
|
||||
ControlPanelConfig,
|
||||
ControlStateMapping,
|
||||
ControlSubSectionHeader,
|
||||
D3_FORMAT_DOCS,
|
||||
D3_FORMAT_OPTIONS,
|
||||
@@ -197,15 +196,6 @@ const config: ControlPanelConfig = {
|
||||
],
|
||||
},
|
||||
],
|
||||
onInit(state: ControlStateMapping) {
|
||||
return {
|
||||
...state,
|
||||
row_limit: {
|
||||
...state.row_limit,
|
||||
value: state.row_limit.default,
|
||||
},
|
||||
};
|
||||
},
|
||||
formDataOverrides: formData => ({
|
||||
...formData,
|
||||
metric: getStandardizedControls().shiftMetric(),
|
||||
|
||||
@@ -33,8 +33,8 @@ import {
|
||||
t,
|
||||
tooltipHtml,
|
||||
} from '@superset-ui/core';
|
||||
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
|
||||
import { CallbackDataParams } from 'echarts/types/src/util/types';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
Cartesian2dCoordSys,
|
||||
EchartsGanttChartProps,
|
||||
@@ -325,6 +325,7 @@ export default function transformProps(chartProps: EchartsGanttChartProps) {
|
||||
show: true,
|
||||
position: 'start',
|
||||
formatter: '{b}',
|
||||
color: theme.colorText,
|
||||
},
|
||||
data: categoryLines,
|
||||
},
|
||||
|
||||
@@ -47,7 +47,10 @@ import {
|
||||
isDerivedSeries,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import type { LineStyleOption } from 'echarts/types/src/util/types';
|
||||
import type {
|
||||
LineStyleOption,
|
||||
CallbackDataParams,
|
||||
} from 'echarts/types/src/util/types';
|
||||
import type { SeriesOption } from 'echarts';
|
||||
import {
|
||||
EchartsTimeseriesChartProps,
|
||||
@@ -575,16 +578,31 @@ export default function transformProps(
|
||||
const xValue: number = richTooltip
|
||||
? params[0].value[xIndex]
|
||||
: params.value[xIndex];
|
||||
const forecastValue: any[] = richTooltip ? params : [params];
|
||||
const forecastValue: CallbackDataParams[] = richTooltip
|
||||
? params
|
||||
: [params];
|
||||
const sortedKeys = extractTooltipKeys(
|
||||
forecastValue,
|
||||
yIndex,
|
||||
richTooltip,
|
||||
tooltipSortByMetric,
|
||||
);
|
||||
const filteredForecastValue = forecastValue.filter(
|
||||
(item: CallbackDataParams) =>
|
||||
!annotationLayers.some(
|
||||
(annotation: AnnotationLayer) =>
|
||||
item.seriesName === annotation.name,
|
||||
),
|
||||
);
|
||||
const forecastValues: Record<string, ForecastValue> =
|
||||
extractForecastValuesFromTooltipParams(forecastValue, isHorizontal);
|
||||
|
||||
const filteredForecastValues: Record<string, ForecastValue> =
|
||||
extractForecastValuesFromTooltipParams(
|
||||
filteredForecastValue,
|
||||
isHorizontal,
|
||||
);
|
||||
|
||||
const isForecast = Object.values(forecastValues).some(
|
||||
value =>
|
||||
value.forecastTrend || value.forecastLower || value.forecastUpper,
|
||||
@@ -595,7 +613,7 @@ export default function transformProps(
|
||||
: (getCustomFormatter(customFormatters, metrics) ?? defaultFormatter);
|
||||
|
||||
const rows: string[][] = [];
|
||||
const total = Object.values(forecastValues).reduce(
|
||||
const total = Object.values(filteredForecastValues).reduce(
|
||||
(acc, value) =>
|
||||
value.observation !== undefined ? acc + value.observation : acc,
|
||||
0,
|
||||
@@ -617,7 +635,16 @@ export default function transformProps(
|
||||
seriesName: key,
|
||||
formatter,
|
||||
});
|
||||
if (showPercentage && value.observation !== undefined) {
|
||||
|
||||
const annotationRow = annotationLayers.some(
|
||||
item => item.name === key,
|
||||
);
|
||||
|
||||
if (
|
||||
showPercentage &&
|
||||
value.observation !== undefined &&
|
||||
!annotationRow
|
||||
) {
|
||||
row.push(
|
||||
percentFormatter.format(value.observation / (total || 1)),
|
||||
);
|
||||
|
||||
@@ -23,8 +23,7 @@ import {
|
||||
SqlaFormData,
|
||||
supersetTheme,
|
||||
} from '@superset-ui/core';
|
||||
import { EchartsBubbleChartProps } from 'plugins/plugin-chart-echarts/src/Bubble/types';
|
||||
|
||||
import { EchartsBubbleChartProps } from '../../src/Bubble/types';
|
||||
import transformProps, { formatTooltip } from '../../src/Bubble/transformProps';
|
||||
|
||||
const defaultFormData: SqlaFormData = {
|
||||
|
||||
@@ -257,6 +257,7 @@ describe('Gantt transformProps', () => {
|
||||
show: true,
|
||||
position: 'start',
|
||||
formatter: '{b}',
|
||||
color: 'rgba(0,0,0,0.88)',
|
||||
},
|
||||
lineStyle: expect.objectContaining({
|
||||
color: '#00000000',
|
||||
|
||||
@@ -2,21 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../packages/superset-ui-chart-controls"
|
||||
},
|
||||
{
|
||||
"path": "../../../packages/superset-ui-core"
|
||||
}
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@superset-ui/core/components": ["../../packages/superset-ui-core/src/components"]
|
||||
}
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
*/
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { SafeMarkdown } from '@superset-ui/core/components';
|
||||
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
|
||||
import Handlebars from 'handlebars';
|
||||
import dayjs from 'dayjs';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { isPlainObject } from 'lodash';
|
||||
import Helpers from 'just-handlebars-helpers';
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@superset-ui/core/components": ["../../packages/superset-ui-core/src/components"]
|
||||
}
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"exclude": ["src/**/*.test.*", "src/**/*.stories.*"],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"xss": "^1.0.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "../../../"
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@superset-ui/core/components": ["../../packages/superset-ui-core/src/components"]
|
||||
}
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
"include": ["**/*", "../types/**/*", "../../../types/**/*"]
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"baseUrl": "."
|
||||
"baseUrl": "../..",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["lib", "test"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*"],
|
||||
"exclude": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.test.*",
|
||||
"src/**/*.stories.*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../packages/superset-core" },
|
||||
{ "path": "../../packages/superset-ui-core" },
|
||||
{ "path": "../../packages/superset-ui-chart-controls" }
|
||||
]
|
||||
|
||||
@@ -258,20 +258,36 @@ export const dashboardWithFilter = {
|
||||
type: DASHBOARD_ROOT_TYPE,
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
children: [DASHBOARD_GRID_ID],
|
||||
meta: {
|
||||
chartId: 0,
|
||||
height: 0,
|
||||
uuid: '',
|
||||
width: 0,
|
||||
},
|
||||
},
|
||||
|
||||
[DASHBOARD_GRID_ID]: {
|
||||
type: DASHBOARD_GRID_TYPE,
|
||||
id: DASHBOARD_GRID_ID,
|
||||
children: ['ROW_ID'],
|
||||
meta: {},
|
||||
meta: {
|
||||
chartId: 0,
|
||||
height: 0,
|
||||
uuid: '',
|
||||
width: 0,
|
||||
},
|
||||
},
|
||||
|
||||
[DASHBOARD_HEADER_ID]: {
|
||||
type: DASHBOARD_HEADER_TYPE,
|
||||
id: DASHBOARD_HEADER_ID,
|
||||
children: [],
|
||||
meta: {
|
||||
text: 'New dashboard',
|
||||
chartId: 0,
|
||||
height: 0,
|
||||
uuid: '',
|
||||
width: 0,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -285,6 +301,7 @@ export const dashboardWithFilter = {
|
||||
...newComponentFactory(CHART_TYPE),
|
||||
id: 'FILTER_ID',
|
||||
meta: {
|
||||
...newComponentFactory(CHART_TYPE).meta,
|
||||
chartId: filterId,
|
||||
width: 3,
|
||||
height: 10,
|
||||
@@ -296,6 +313,7 @@ export const dashboardWithFilter = {
|
||||
...newComponentFactory(CHART_TYPE),
|
||||
id: 'CHART_ID',
|
||||
meta: {
|
||||
...newComponentFactory(CHART_TYPE).meta,
|
||||
chartId,
|
||||
width: 3,
|
||||
height: 10,
|
||||
|
||||
@@ -42,8 +42,8 @@ import {
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
} from '@superset-ui/core';
|
||||
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import dayjs from 'dayjs';
|
||||
import rison from 'rison';
|
||||
import { createDatasource } from 'src/SqlLab/actions/sqlLab';
|
||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
|
||||
@@ -182,7 +182,6 @@ test('should handle matrixify-related form data changes', () => {
|
||||
const initialProps = {
|
||||
...requiredProps,
|
||||
formData: {
|
||||
matrixify_enabled: false,
|
||||
regular_control: 'value1',
|
||||
},
|
||||
queriesResponse: [{ data: 'current' }],
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { ReactNode, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { TableInstance, Row } from 'react-table';
|
||||
import { TableInstance, Row, UseRowSelectRowProps } from 'react-table';
|
||||
import { styled } from '@superset-ui/core';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -65,7 +65,7 @@ export default function CardCollection({
|
||||
}: CardCollectionProps) {
|
||||
function handleClick(
|
||||
event: ReactMouseEvent<HTMLDivElement, MouseEvent>,
|
||||
toggleRowSelected: Row['toggleRowSelected'],
|
||||
toggleRowSelected: (value?: boolean) => void,
|
||||
) {
|
||||
if (bulkSelectEnabled) {
|
||||
event.preventDefault();
|
||||
@@ -89,11 +89,18 @@ export default function CardCollection({
|
||||
return (
|
||||
<CardWrapper
|
||||
className={cx({
|
||||
'card-selected': bulkSelectEnabled && row.isSelected,
|
||||
'card-selected':
|
||||
bulkSelectEnabled &&
|
||||
(row as Row & UseRowSelectRowProps<any>).isSelected,
|
||||
'bulk-select': bulkSelectEnabled,
|
||||
})}
|
||||
key={row.id}
|
||||
onClick={e => handleClick(e, row.toggleRowSelected)}
|
||||
onClick={e =>
|
||||
handleClick(
|
||||
e,
|
||||
(row as Row & UseRowSelectRowProps<any>).toggleRowSelected,
|
||||
)
|
||||
}
|
||||
role="none"
|
||||
>
|
||||
{renderCard({ ...row.original, loading })}
|
||||
|
||||
@@ -419,7 +419,7 @@ export function ListView<T extends object = any>({
|
||||
cta
|
||||
onClick={() =>
|
||||
action.onSelect(
|
||||
selectedFlatRows.map(r => r.original),
|
||||
selectedFlatRows.map((r: any) => r.original),
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -475,10 +475,10 @@ export function ListView<T extends object = any>({
|
||||
bulkSelectEnabled={bulkSelectEnabled}
|
||||
selectedFlatRows={selectedFlatRows}
|
||||
toggleRowSelected={(rowId, value) => {
|
||||
const row = rows.find(r => r.id === rowId);
|
||||
const row = rows.find((r: any) => r.id === rowId);
|
||||
if (row) {
|
||||
prepareRow(row);
|
||||
row.toggleRowSelected(value);
|
||||
(row as any).toggleRowSelected(value);
|
||||
}
|
||||
}}
|
||||
toggleAllRowsSelected={toggleAllRowsSelected}
|
||||
|
||||
@@ -273,23 +273,23 @@ export function useListViewState({
|
||||
} = useTable(
|
||||
{
|
||||
columns: columnsWithSelect,
|
||||
count,
|
||||
data,
|
||||
disableFilters: true,
|
||||
disableSortRemove: true,
|
||||
initialState,
|
||||
initialState: initialState as any,
|
||||
manualFilters: true,
|
||||
manualPagination: true,
|
||||
manualSortBy: true,
|
||||
autoResetFilters: false,
|
||||
pageCount: Math.ceil(count / initialPageSize),
|
||||
...({ count } as any),
|
||||
},
|
||||
useFilters,
|
||||
useSortBy,
|
||||
usePagination,
|
||||
useRowState,
|
||||
useRowSelect,
|
||||
);
|
||||
) as any;
|
||||
|
||||
const [internalFilters, setInternalFilters] = useState<InternalFilter[]>(
|
||||
query.filters && initialFilters.length
|
||||
|
||||
@@ -17,8 +17,26 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { ADD_TOAST, REMOVE_TOAST } from './actions';
|
||||
import { ToastMeta } from './types';
|
||||
|
||||
export default function messageToastsReducer(toasts = [], action) {
|
||||
interface AddToastAction {
|
||||
type: typeof ADD_TOAST;
|
||||
payload: ToastMeta;
|
||||
}
|
||||
|
||||
interface RemoveToastAction {
|
||||
type: typeof REMOVE_TOAST;
|
||||
payload: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
type ToastAction = AddToastAction | RemoveToastAction;
|
||||
|
||||
export default function messageToastsReducer(
|
||||
toasts: ToastMeta[] = [],
|
||||
action: ToastAction,
|
||||
): ToastMeta[] {
|
||||
switch (action.type) {
|
||||
case ADD_TOAST: {
|
||||
const { payload: toast } = action;
|
||||
@@ -20,7 +20,7 @@ import { useState } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { Tag } from 'src/components/Tag';
|
||||
import type { CheckableTagProps } from 'src/components/Tag';
|
||||
import TagType from 'src/types/TagType';
|
||||
import type { TagType } from 'src/types/TagType';
|
||||
|
||||
export default {
|
||||
title: 'Components/Tag',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import TagType from 'src/types/TagType';
|
||||
import type { TagType } from 'src/types/TagType';
|
||||
import { Tag } from '.';
|
||||
|
||||
const mockedProps: TagType = {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import { styled } from '@superset-ui/core';
|
||||
import { Link } from 'react-router-dom';
|
||||
import TagType from 'src/types/TagType';
|
||||
import type { TagType } from 'src/types/TagType';
|
||||
import { Tag as AntdTag } from '@superset-ui/core/components/Tag';
|
||||
import { Tooltip } from '@superset-ui/core/components/Tooltip';
|
||||
import type { TagProps } from 'antd/es';
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
SupersetClient,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import Tag from 'src/types/TagType';
|
||||
import type { TagType } from 'src/types/TagType';
|
||||
|
||||
import rison from 'rison';
|
||||
import { cacheWrapper } from 'src/utils/cacheWrapper';
|
||||
@@ -43,7 +43,7 @@ type SelectTagsValue = {
|
||||
};
|
||||
|
||||
export const tagToSelectOption = (
|
||||
tag: Tag & { table_name: string },
|
||||
tag: TagType & { table_name: string },
|
||||
): SelectTagsValue => ({
|
||||
value: tag.id,
|
||||
label: tag.name,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import { styled } from '@superset-ui/core';
|
||||
import TagType from 'src/types/TagType';
|
||||
import type { TagType } from 'src/types/TagType';
|
||||
import { Tag } from 'src/components/Tag';
|
||||
|
||||
export type TagsListProps = {
|
||||
|
||||
@@ -26,9 +26,40 @@ import {
|
||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
import { Dispatch } from 'redux';
|
||||
import { Slice } from '../types';
|
||||
import { HYDRATE_DASHBOARD } from './hydrate';
|
||||
|
||||
const FETCH_SLICES_PAGE_SIZE = 200;
|
||||
|
||||
// State types
|
||||
export interface SliceEntitiesState {
|
||||
slices: { [id: number]: Slice };
|
||||
isLoading: boolean;
|
||||
errorMessage: string | null;
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
// Action types
|
||||
export type SliceEntitiesActionPayload =
|
||||
| {
|
||||
type: typeof ADD_SLICES;
|
||||
payload: { slices: { [id: number]: Slice } };
|
||||
}
|
||||
| {
|
||||
type: typeof SET_SLICES;
|
||||
payload: { slices: { [id: number]: Slice } };
|
||||
}
|
||||
| {
|
||||
type: typeof FETCH_ALL_SLICES_STARTED;
|
||||
}
|
||||
| {
|
||||
type: typeof FETCH_ALL_SLICES_FAILED;
|
||||
payload: { error: string };
|
||||
}
|
||||
| {
|
||||
type: typeof HYDRATE_DASHBOARD;
|
||||
data: { sliceEntities: SliceEntitiesState };
|
||||
};
|
||||
|
||||
export function getDatasourceParameter(
|
||||
datasourceId: number,
|
||||
datasourceType: DatasourceType,
|
||||
|
||||
@@ -170,7 +170,9 @@ const SliceAddedBadge: FC<{ placeholder?: HTMLDivElement }> = ({
|
||||
const AddSliceCard: FC<{
|
||||
datasourceUrl?: string;
|
||||
datasourceName?: string;
|
||||
innerRef?: RefObject<HTMLDivElement>;
|
||||
innerRef?:
|
||||
| RefObject<HTMLDivElement>
|
||||
| ((node: HTMLDivElement | null) => void);
|
||||
isSelected?: boolean;
|
||||
lastModified?: string;
|
||||
sliceName: string;
|
||||
@@ -197,7 +199,7 @@ const AddSliceCard: FC<{
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={innerRef} style={style}>
|
||||
<div ref={innerRef as any} style={style}>
|
||||
<div
|
||||
data-test="chart-card"
|
||||
css={(theme: Theme) => css`
|
||||
|
||||
@@ -50,6 +50,7 @@ import { Dispatch } from 'redux';
|
||||
import { Slice } from 'src/dashboard/types';
|
||||
import { withTheme, Theme } from '@emotion/react';
|
||||
import { navigateTo } from 'src/utils/navigationUtils';
|
||||
import type { ConnectDragSource } from 'react-dnd';
|
||||
import AddSliceCard from './AddSliceCard';
|
||||
import AddSliceDragPreview from './dnd/AddSliceDragPreview';
|
||||
import { DragDroppable } from './dnd/DragDroppable';
|
||||
@@ -312,7 +313,7 @@ class SliceAdder extends Component<SliceAdderProps, SliceAdderState> {
|
||||
// actual style should be applied to nested AddSliceCard component
|
||||
style={{}}
|
||||
>
|
||||
{({ dragSourceRef }) => (
|
||||
{({ dragSourceRef }: { dragSourceRef: ConnectDragSource }) => (
|
||||
<AddSliceCard
|
||||
innerRef={dragSourceRef}
|
||||
style={style}
|
||||
|
||||
@@ -1,85 +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 handleHover from './handleHover';
|
||||
import handleDrop from './handleDrop';
|
||||
|
||||
// note: the 'type' hook is not useful for us as dropping is contingent on other properties
|
||||
const TYPE = 'DRAG_DROPPABLE';
|
||||
|
||||
export const dragConfig = [
|
||||
TYPE,
|
||||
{
|
||||
canDrag(props) {
|
||||
return !props.disableDragDrop;
|
||||
},
|
||||
|
||||
// this defines the dragging item object returned by monitor.getItem()
|
||||
beginDrag(props /* , monitor, component */) {
|
||||
const { component, index, parentComponent = {} } = props;
|
||||
return {
|
||||
type: component.type,
|
||||
id: component.id,
|
||||
meta: component.meta,
|
||||
index,
|
||||
parentId: parentComponent.id,
|
||||
parentType: parentComponent.type,
|
||||
};
|
||||
},
|
||||
},
|
||||
function dragStateToProps(connect, monitor) {
|
||||
return {
|
||||
dragSourceRef: connect.dragSource(),
|
||||
dragPreviewRef: connect.dragPreview(),
|
||||
isDragging: monitor.isDragging(),
|
||||
dragComponentType: monitor.getItem()?.type,
|
||||
dragComponentId: monitor.getItem()?.id,
|
||||
};
|
||||
},
|
||||
];
|
||||
|
||||
export const dropConfig = [
|
||||
TYPE,
|
||||
{
|
||||
canDrop(props) {
|
||||
return !props.disableDragDrop;
|
||||
},
|
||||
hover(props, monitor, component) {
|
||||
if (component && component.mounted) {
|
||||
handleHover(props, monitor, component);
|
||||
}
|
||||
},
|
||||
// note:
|
||||
// the react-dnd api requires that the drop() method return a result or undefined
|
||||
// monitor.didDrop() cannot be used because it returns true only for the most-nested target
|
||||
drop(props, monitor, component) {
|
||||
const dropResult = monitor.getDropResult();
|
||||
if ((!dropResult || !dropResult.destination) && component.mounted) {
|
||||
return handleDrop(props, monitor, component);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
function dropStateToProps(connect, monitor) {
|
||||
return {
|
||||
droppableRef: connect.dropTarget(),
|
||||
isDraggingOver: monitor.isOver(),
|
||||
isDraggingOverShallow: monitor.isOver({ shallow: true }),
|
||||
};
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* 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 {
|
||||
DragSourceMonitor,
|
||||
DropTargetMonitor,
|
||||
ConnectDragSource,
|
||||
ConnectDragPreview,
|
||||
ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
import { LayoutItem, ComponentType } from 'src/dashboard/types';
|
||||
import handleHover from './handleHover';
|
||||
import handleDrop from './handleDrop';
|
||||
|
||||
// note: the 'type' hook is not useful for us as dropping is contingent on other properties
|
||||
const TYPE = 'DRAG_DROPPABLE';
|
||||
|
||||
export interface DragDroppableProps {
|
||||
component: LayoutItem;
|
||||
parentComponent?: LayoutItem;
|
||||
index: number;
|
||||
disableDragDrop: boolean;
|
||||
onDrop?: (dropResult: DropResult) => void;
|
||||
onHover?: () => void;
|
||||
dropToChild?: boolean | ((draggingItem: DragItem) => boolean);
|
||||
}
|
||||
|
||||
export interface DragItem {
|
||||
type: ComponentType;
|
||||
id: string;
|
||||
meta: LayoutItem['meta'];
|
||||
index: number;
|
||||
parentId?: string;
|
||||
parentType?: ComponentType;
|
||||
}
|
||||
|
||||
export interface DropResult {
|
||||
source: {
|
||||
id: string;
|
||||
type: ComponentType;
|
||||
index: number;
|
||||
};
|
||||
dragging: {
|
||||
id: string;
|
||||
type: ComponentType;
|
||||
meta: LayoutItem['meta'];
|
||||
};
|
||||
destination?: {
|
||||
id: string;
|
||||
type: ComponentType;
|
||||
index: number;
|
||||
};
|
||||
position?: string;
|
||||
}
|
||||
|
||||
export interface DragStateProps {
|
||||
dragSourceRef: ConnectDragSource;
|
||||
dragPreviewRef: ConnectDragPreview;
|
||||
isDragging: boolean;
|
||||
dragComponentType?: ComponentType;
|
||||
dragComponentId?: string;
|
||||
}
|
||||
|
||||
export interface DropStateProps {
|
||||
droppableRef: ConnectDropTarget;
|
||||
isDraggingOver: boolean;
|
||||
isDraggingOverShallow: boolean;
|
||||
}
|
||||
|
||||
export interface DragDroppableComponent {
|
||||
mounted: boolean;
|
||||
props: DragDroppableProps;
|
||||
setState: (stateUpdate: () => { dropIndicator: string | null }) => void;
|
||||
}
|
||||
|
||||
export const dragConfig: [
|
||||
string,
|
||||
{
|
||||
canDrag: (props: DragDroppableProps) => boolean;
|
||||
beginDrag: (props: DragDroppableProps) => DragItem;
|
||||
},
|
||||
(connect: any, monitor: DragSourceMonitor) => DragStateProps,
|
||||
] = [
|
||||
TYPE,
|
||||
{
|
||||
canDrag(props: DragDroppableProps): boolean {
|
||||
return !props.disableDragDrop;
|
||||
},
|
||||
|
||||
// this defines the dragging item object returned by monitor.getItem()
|
||||
beginDrag(props: DragDroppableProps): DragItem {
|
||||
const { component, index, parentComponent } = props;
|
||||
return {
|
||||
type: component.type,
|
||||
id: component.id,
|
||||
meta: component.meta,
|
||||
index,
|
||||
parentId: parentComponent?.id,
|
||||
parentType: parentComponent?.type,
|
||||
};
|
||||
},
|
||||
},
|
||||
function dragStateToProps(
|
||||
connect: any,
|
||||
monitor: DragSourceMonitor,
|
||||
): DragStateProps {
|
||||
return {
|
||||
dragSourceRef: connect.dragSource(),
|
||||
dragPreviewRef: connect.dragPreview(),
|
||||
isDragging: monitor.isDragging(),
|
||||
dragComponentType: monitor.getItem()?.type as ComponentType,
|
||||
dragComponentId: monitor.getItem()?.id as string,
|
||||
};
|
||||
},
|
||||
];
|
||||
|
||||
export const dropConfig: [
|
||||
string,
|
||||
{
|
||||
canDrop: (props: DragDroppableProps) => boolean;
|
||||
hover: (
|
||||
props: DragDroppableProps,
|
||||
monitor: DropTargetMonitor,
|
||||
component: DragDroppableComponent,
|
||||
) => void;
|
||||
drop: (
|
||||
props: DragDroppableProps,
|
||||
monitor: DropTargetMonitor,
|
||||
component: DragDroppableComponent,
|
||||
) => DropResult | undefined;
|
||||
},
|
||||
(connect: any, monitor: DropTargetMonitor) => DropStateProps,
|
||||
] = [
|
||||
TYPE,
|
||||
{
|
||||
canDrop(props: DragDroppableProps): boolean {
|
||||
return !props.disableDragDrop;
|
||||
},
|
||||
hover(
|
||||
props: DragDroppableProps,
|
||||
monitor: DropTargetMonitor,
|
||||
component: DragDroppableComponent,
|
||||
): void {
|
||||
if (component && component.mounted) {
|
||||
handleHover(props, monitor, component);
|
||||
}
|
||||
},
|
||||
// note:
|
||||
// the react-dnd api requires that the drop() method return a result or undefined
|
||||
// monitor.didDrop() cannot be used because it returns true only for the most-nested target
|
||||
drop(
|
||||
props: DragDroppableProps,
|
||||
monitor: DropTargetMonitor,
|
||||
component: DragDroppableComponent,
|
||||
): DropResult | undefined {
|
||||
const dropResult = monitor.getDropResult() as DropResult | null;
|
||||
if ((!dropResult || !dropResult.destination) && component.mounted) {
|
||||
return handleDrop(props, monitor, component);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
function dropStateToProps(
|
||||
connect: any,
|
||||
monitor: DropTargetMonitor,
|
||||
): DropStateProps {
|
||||
return {
|
||||
droppableRef: connect.dropTarget(),
|
||||
isDraggingOver: monitor.isOver(),
|
||||
isDraggingOverShallow: monitor.isOver({ shallow: true }),
|
||||
};
|
||||
},
|
||||
];
|
||||
@@ -22,6 +22,7 @@ import backgroundStyleOptions from 'src/dashboard/util/backgroundStyleOptions';
|
||||
import cx from 'classnames';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { ResizeCallback, ResizeStartCallback } from 're-resizable';
|
||||
import type { ConnectDragSource } from 'react-dnd';
|
||||
import { Draggable } from '../../dnd/DragDroppable';
|
||||
import { COLUMN_TYPE, ROW_TYPE } from '../../../util/componentTypes';
|
||||
import WithPopoverMenu from '../../menu/WithPopoverMenu';
|
||||
@@ -119,7 +120,7 @@ const DynamicComponent: FC<DynamicComponentProps> = ({
|
||||
onDrop={handleComponentDrop}
|
||||
editMode={editMode}
|
||||
>
|
||||
{({ dragSourceRef }) => (
|
||||
{({ dragSourceRef }: { dragSourceRef: ConnectDragSource }) => (
|
||||
<WithPopoverMenu
|
||||
menuItems={[
|
||||
<BackgroundStyleDropdown
|
||||
|
||||
@@ -20,6 +20,7 @@ import { PureComponent } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { css, styled } from '@superset-ui/core';
|
||||
import { DragDroppable } from 'src/dashboard/components/dnd/DragDroppable';
|
||||
import type { ConnectDragSource } from 'react-dnd';
|
||||
import { NEW_COMPONENTS_SOURCE_ID } from 'src/dashboard/util/constants';
|
||||
import { NEW_COMPONENT_SOURCE_TYPE } from 'src/dashboard/util/componentTypes';
|
||||
|
||||
@@ -81,7 +82,7 @@ export default class DraggableNewComponent extends PureComponent<DraggableNewCom
|
||||
depth={0}
|
||||
editMode
|
||||
>
|
||||
{({ dragSourceRef }) => (
|
||||
{({ dragSourceRef }: { dragSourceRef: ConnectDragSource }) => (
|
||||
<NewComponent ref={dragSourceRef} data-test="new-component">
|
||||
<NewComponentPlaceholder
|
||||
className={cx('new-component-placeholder', className)}
|
||||
|
||||
@@ -28,11 +28,38 @@ import {
|
||||
DASHBOARD_GRID_ID,
|
||||
} from '../util/constants';
|
||||
|
||||
export default {
|
||||
import type { DashboardLayout, LayoutItemMeta } from '../types';
|
||||
|
||||
// Create minimal meta objects that satisfy the LayoutItemMeta type requirements
|
||||
const rootMeta: LayoutItemMeta = {
|
||||
chartId: 0,
|
||||
height: 0,
|
||||
uuid: '',
|
||||
width: 0,
|
||||
};
|
||||
|
||||
const gridMeta: LayoutItemMeta = {
|
||||
chartId: 0,
|
||||
height: 0,
|
||||
uuid: '',
|
||||
width: 0,
|
||||
};
|
||||
|
||||
const headerMeta: LayoutItemMeta = {
|
||||
chartId: 0,
|
||||
height: 0,
|
||||
uuid: '',
|
||||
width: 0,
|
||||
text: 'New dashboard',
|
||||
};
|
||||
|
||||
const emptyDashboardLayout: DashboardLayout = {
|
||||
[DASHBOARD_ROOT_ID]: {
|
||||
type: DASHBOARD_ROOT_TYPE,
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
children: [DASHBOARD_GRID_ID],
|
||||
parents: [],
|
||||
meta: rootMeta,
|
||||
},
|
||||
|
||||
[DASHBOARD_GRID_ID]: {
|
||||
@@ -40,14 +67,16 @@ export default {
|
||||
id: DASHBOARD_GRID_ID,
|
||||
children: [],
|
||||
parents: [DASHBOARD_ROOT_ID],
|
||||
meta: {},
|
||||
meta: gridMeta,
|
||||
},
|
||||
|
||||
[DASHBOARD_HEADER_ID]: {
|
||||
type: HEADER_TYPE,
|
||||
id: DASHBOARD_HEADER_ID,
|
||||
meta: {
|
||||
text: 'New dashboard',
|
||||
},
|
||||
children: [],
|
||||
parents: [],
|
||||
meta: headerMeta,
|
||||
},
|
||||
};
|
||||
|
||||
export default emptyDashboardLayout;
|
||||
@@ -23,10 +23,12 @@ import {
|
||||
FETCH_ALL_SLICES_STARTED,
|
||||
ADD_SLICES,
|
||||
SET_SLICES,
|
||||
SliceEntitiesState,
|
||||
SliceEntitiesActionPayload,
|
||||
} from '../actions/sliceEntities';
|
||||
import { HYDRATE_DASHBOARD } from '../actions/hydrate';
|
||||
|
||||
export const initSliceEntities = {
|
||||
export const initSliceEntities: SliceEntitiesState = {
|
||||
slices: {},
|
||||
isLoading: true,
|
||||
errorMessage: null,
|
||||
@@ -34,37 +36,34 @@ export const initSliceEntities = {
|
||||
};
|
||||
|
||||
export default function sliceEntitiesReducer(
|
||||
state = initSliceEntities,
|
||||
action,
|
||||
) {
|
||||
const actionHandlers = {
|
||||
[HYDRATE_DASHBOARD]() {
|
||||
state: SliceEntitiesState = initSliceEntities,
|
||||
action: SliceEntitiesActionPayload,
|
||||
): SliceEntitiesState {
|
||||
switch (action.type) {
|
||||
case HYDRATE_DASHBOARD:
|
||||
return {
|
||||
...action.data.sliceEntities,
|
||||
};
|
||||
},
|
||||
[FETCH_ALL_SLICES_STARTED]() {
|
||||
case FETCH_ALL_SLICES_STARTED:
|
||||
return {
|
||||
...state,
|
||||
isLoading: true,
|
||||
};
|
||||
},
|
||||
[ADD_SLICES]() {
|
||||
case ADD_SLICES:
|
||||
return {
|
||||
...state,
|
||||
isLoading: false,
|
||||
slices: { ...state.slices, ...action.payload.slices },
|
||||
lastUpdated: new Date().getTime(),
|
||||
};
|
||||
},
|
||||
[SET_SLICES]() {
|
||||
case SET_SLICES:
|
||||
return {
|
||||
...state,
|
||||
isLoading: false,
|
||||
slices: { ...action.payload.slices },
|
||||
lastUpdated: new Date().getTime(),
|
||||
};
|
||||
},
|
||||
[FETCH_ALL_SLICES_FAILED]() {
|
||||
case FETCH_ALL_SLICES_FAILED:
|
||||
return {
|
||||
...state,
|
||||
isLoading: false,
|
||||
@@ -72,11 +71,7 @@ export default function sliceEntitiesReducer(
|
||||
errorMessage:
|
||||
action.payload.error || t('Could not fetch all saved charts'),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
if (action.type in actionHandlers) {
|
||||
return actionHandlers[action.type]();
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
@@ -1,126 +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 { isEmpty } from 'lodash';
|
||||
import { mapValues, flow, keyBy } from 'lodash/fp';
|
||||
import {
|
||||
getChartIdAndColumnFromFilterKey,
|
||||
getDashboardFilterKey,
|
||||
} from './getDashboardFilterKey';
|
||||
import { CHART_TYPE } from './componentTypes';
|
||||
import { DASHBOARD_FILTER_SCOPE_GLOBAL } from '../reducers/dashboardFilters';
|
||||
|
||||
let activeFilters = {};
|
||||
let appliedFilterValuesByChart = {};
|
||||
let allComponents = {};
|
||||
|
||||
// output: { [id_column]: { values, scope } }
|
||||
export function getActiveFilters() {
|
||||
return activeFilters;
|
||||
}
|
||||
|
||||
// this function is to find all filter values applied to a chart,
|
||||
// it goes through all active filters and their scopes.
|
||||
// return: { [column]: array of selected values }
|
||||
export function getAppliedFilterValues(chartId, filters) {
|
||||
// use cached data if possible
|
||||
if (!(chartId in appliedFilterValuesByChart)) {
|
||||
const applicableFilters = Object.entries(filters || activeFilters).filter(
|
||||
([, { scope: chartIds }]) => chartIds.includes(chartId),
|
||||
);
|
||||
appliedFilterValuesByChart[chartId] = flow(
|
||||
keyBy(
|
||||
([filterKey]) => getChartIdAndColumnFromFilterKey(filterKey).column,
|
||||
),
|
||||
mapValues(([, { values }]) => values),
|
||||
)(applicableFilters);
|
||||
}
|
||||
return appliedFilterValuesByChart[chartId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use src/dashboard/util/getChartIdsInFilterScope instead
|
||||
*/
|
||||
export function getChartIdsInFilterScope({ filterScope }) {
|
||||
function traverse(chartIds = [], component = {}, immuneChartIds = []) {
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
component.type === CHART_TYPE &&
|
||||
component.meta &&
|
||||
component.meta.chartId &&
|
||||
!immuneChartIds.includes(component.meta.chartId)
|
||||
) {
|
||||
chartIds.push(component.meta.chartId);
|
||||
} else if (component.children) {
|
||||
component.children.forEach(child =>
|
||||
traverse(chartIds, allComponents[child], immuneChartIds),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const chartIds = [];
|
||||
const { scope: scopeComponentIds, immune: immuneChartIds } =
|
||||
filterScope || DASHBOARD_FILTER_SCOPE_GLOBAL;
|
||||
scopeComponentIds.forEach(componentId =>
|
||||
traverse(chartIds, allComponents[componentId], immuneChartIds),
|
||||
);
|
||||
|
||||
return chartIds;
|
||||
}
|
||||
|
||||
// non-empty filter fields in dashboardFilters,
|
||||
// activeFilters map contains selected values and filter scope.
|
||||
// values: array of selected values
|
||||
// scope: array of chartIds that applicable to the filter field.
|
||||
export function buildActiveFilters({ dashboardFilters = {}, components = {} }) {
|
||||
// clear cache
|
||||
if (!isEmpty(components)) {
|
||||
allComponents = components;
|
||||
}
|
||||
appliedFilterValuesByChart = {};
|
||||
activeFilters = Object.values(dashboardFilters).reduce((result, filter) => {
|
||||
const { chartId, columns, scopes } = filter;
|
||||
const nonEmptyFilters = {};
|
||||
|
||||
Object.keys(columns).forEach(column => {
|
||||
if (
|
||||
Array.isArray(columns[column])
|
||||
? columns[column].length
|
||||
: columns[column] !== undefined
|
||||
) {
|
||||
// remove filter itself
|
||||
const scope = getChartIdsInFilterScope({
|
||||
filterScope: scopes[column],
|
||||
}).filter(id => chartId !== id);
|
||||
|
||||
nonEmptyFilters[getDashboardFilterKey({ chartId, column })] = {
|
||||
values: columns[column],
|
||||
scope,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
...nonEmptyFilters,
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
211
superset-frontend/src/dashboard/util/activeDashboardFilters.ts
Normal file
211
superset-frontend/src/dashboard/util/activeDashboardFilters.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* 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 { isEmpty } from 'lodash';
|
||||
import { mapValues, flow, keyBy } from 'lodash/fp';
|
||||
import {
|
||||
JsonValue,
|
||||
DataRecordFilters,
|
||||
DataRecordValue,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
getChartIdAndColumnFromFilterKey,
|
||||
getDashboardFilterKey,
|
||||
} from './getDashboardFilterKey';
|
||||
import { CHART_TYPE } from './componentTypes';
|
||||
import { DASHBOARD_FILTER_SCOPE_GLOBAL } from '../reducers/dashboardFilters';
|
||||
import { LayoutItem } from '../types';
|
||||
|
||||
// Type definitions for filters
|
||||
interface FilterScope {
|
||||
scope: string[];
|
||||
immune?: number[];
|
||||
}
|
||||
|
||||
interface DashboardFilterColumn {
|
||||
[column: string]: JsonValue[] | JsonValue;
|
||||
}
|
||||
|
||||
interface DashboardFilterScopes {
|
||||
[column: string]: FilterScope;
|
||||
}
|
||||
|
||||
interface DashboardFilter {
|
||||
chartId: number;
|
||||
columns: DashboardFilterColumn;
|
||||
scopes: DashboardFilterScopes;
|
||||
}
|
||||
|
||||
interface DashboardFilters {
|
||||
[filterId: string]: DashboardFilter;
|
||||
}
|
||||
|
||||
interface Components {
|
||||
[componentId: string]: LayoutItem;
|
||||
}
|
||||
|
||||
interface ActiveFilter {
|
||||
values: JsonValue[] | JsonValue;
|
||||
scope: number[];
|
||||
}
|
||||
|
||||
interface ActiveFilters {
|
||||
[filterKey: string]: ActiveFilter;
|
||||
}
|
||||
|
||||
interface AppliedFilterValuesByChart {
|
||||
[chartId: number]: DataRecordFilters;
|
||||
}
|
||||
|
||||
interface GetChartIdsInFilterScopeProps {
|
||||
filterScope?: FilterScope;
|
||||
}
|
||||
|
||||
interface BuildActiveFiltersProps {
|
||||
dashboardFilters?: DashboardFilters;
|
||||
components?: Components;
|
||||
}
|
||||
|
||||
let activeFilters: ActiveFilters = {};
|
||||
let appliedFilterValuesByChart: AppliedFilterValuesByChart = {};
|
||||
let allComponents: Components = {};
|
||||
|
||||
// output: { [id_column]: { values, scope } }
|
||||
export function getActiveFilters(): ActiveFilters {
|
||||
return activeFilters;
|
||||
}
|
||||
|
||||
// this function is to find all filter values applied to a chart,
|
||||
// it goes through all active filters and their scopes.
|
||||
// return: { [column]: array of selected values }
|
||||
export function getAppliedFilterValues(
|
||||
chartId: number,
|
||||
filters?: ActiveFilters,
|
||||
): DataRecordFilters {
|
||||
// use cached data if possible
|
||||
if (!(chartId in appliedFilterValuesByChart)) {
|
||||
const applicableFilters = Object.entries(filters || activeFilters).filter(
|
||||
([, { scope: chartIds }]) => chartIds.includes(chartId),
|
||||
);
|
||||
appliedFilterValuesByChart[chartId] = flow(
|
||||
keyBy(
|
||||
([filterKey]: [string, ActiveFilter]) =>
|
||||
getChartIdAndColumnFromFilterKey(filterKey).column,
|
||||
),
|
||||
mapValues(([, { values }]: [string, ActiveFilter]) => {
|
||||
// Ensure values is always an array of DataRecordValue
|
||||
if (Array.isArray(values)) {
|
||||
return values.filter(
|
||||
val => val !== null && val !== undefined,
|
||||
) as DataRecordValue[];
|
||||
}
|
||||
// If single value, wrap in array and filter valid values
|
||||
return values !== null && values !== undefined
|
||||
? [values as DataRecordValue]
|
||||
: [];
|
||||
}),
|
||||
)(applicableFilters);
|
||||
}
|
||||
return appliedFilterValuesByChart[chartId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use src/dashboard/util/getChartIdsInFilterScope instead
|
||||
*/
|
||||
export function getChartIdsInFilterScope({
|
||||
filterScope,
|
||||
}: GetChartIdsInFilterScopeProps): number[] {
|
||||
function traverse(
|
||||
chartIds: number[] = [],
|
||||
component: LayoutItem | undefined = undefined,
|
||||
immuneChartIds: number[] = [],
|
||||
): void {
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
component.type === CHART_TYPE &&
|
||||
component.meta &&
|
||||
component.meta.chartId &&
|
||||
!immuneChartIds.includes(component.meta.chartId)
|
||||
) {
|
||||
chartIds.push(component.meta.chartId);
|
||||
} else if (component.children) {
|
||||
component.children.forEach(child =>
|
||||
traverse(chartIds, allComponents[child], immuneChartIds),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const chartIds: number[] = [];
|
||||
const { scope: scopeComponentIds, immune: immuneChartIds = [] } =
|
||||
filterScope || DASHBOARD_FILTER_SCOPE_GLOBAL;
|
||||
scopeComponentIds.forEach(componentId =>
|
||||
traverse(chartIds, allComponents[componentId], immuneChartIds),
|
||||
);
|
||||
|
||||
return chartIds;
|
||||
}
|
||||
|
||||
// non-empty filter fields in dashboardFilters,
|
||||
// activeFilters map contains selected values and filter scope.
|
||||
// values: array of selected values
|
||||
// scope: array of chartIds that applicable to the filter field.
|
||||
export function buildActiveFilters({
|
||||
dashboardFilters = {},
|
||||
components = {},
|
||||
}: BuildActiveFiltersProps): void {
|
||||
// clear cache
|
||||
if (!isEmpty(components)) {
|
||||
allComponents = components;
|
||||
}
|
||||
appliedFilterValuesByChart = {};
|
||||
activeFilters = Object.values(dashboardFilters).reduce(
|
||||
(result: ActiveFilters, filter: DashboardFilter) => {
|
||||
const { chartId, columns, scopes } = filter;
|
||||
const nonEmptyFilters: ActiveFilters = {};
|
||||
|
||||
Object.keys(columns).forEach(column => {
|
||||
if (
|
||||
Array.isArray(columns[column])
|
||||
? (columns[column] as JsonValue[]).length
|
||||
: columns[column] !== undefined
|
||||
) {
|
||||
// remove filter itself
|
||||
const scope = getChartIdsInFilterScope({
|
||||
filterScope: scopes[column],
|
||||
}).filter(id => chartId !== id);
|
||||
|
||||
nonEmptyFilters[
|
||||
getDashboardFilterKey({ chartId: String(chartId), column })
|
||||
] = {
|
||||
values: columns[column],
|
||||
scope,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
...nonEmptyFilters,
|
||||
};
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
||||
@@ -16,17 +16,41 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { DashboardLayout } from '../types';
|
||||
import getFilterScopeNodesTree from './getFilterScopeNodesTree';
|
||||
import getFilterScopeParentNodes from './getFilterScopeParentNodes';
|
||||
import getKeyForFilterScopeTree from './getKeyForFilterScopeTree';
|
||||
import getSelectedChartIdForFilterScopeTree from './getSelectedChartIdForFilterScopeTree';
|
||||
|
||||
interface FilterScopeMapItem {
|
||||
checked?: number[];
|
||||
expanded?: string[];
|
||||
}
|
||||
|
||||
interface FilterScopeMap {
|
||||
[key: string]: FilterScopeMapItem;
|
||||
}
|
||||
|
||||
interface FilterScopeTreeEntry {
|
||||
nodes: any[];
|
||||
nodesFiltered: any[];
|
||||
checked: string[];
|
||||
expanded: string[];
|
||||
}
|
||||
|
||||
interface BuildFilterScopeTreeEntryProps {
|
||||
checkedFilterFields?: string[];
|
||||
activeFilterField?: string;
|
||||
filterScopeMap?: FilterScopeMap;
|
||||
layout?: DashboardLayout;
|
||||
}
|
||||
|
||||
export default function buildFilterScopeTreeEntry({
|
||||
checkedFilterFields = [],
|
||||
activeFilterField,
|
||||
filterScopeMap = {},
|
||||
layout = {},
|
||||
}) {
|
||||
}: BuildFilterScopeTreeEntryProps): Record<string, FilterScopeTreeEntry> {
|
||||
const key = getKeyForFilterScopeTree({
|
||||
checkedFilterFields,
|
||||
activeFilterField,
|
||||
@@ -43,15 +67,15 @@ export default function buildFilterScopeTreeEntry({
|
||||
filterFields: editingList,
|
||||
selectedChartId,
|
||||
});
|
||||
const checkedChartIdSet = new Set();
|
||||
const checkedChartIdSet = new Set<string>();
|
||||
editingList.forEach(filterField => {
|
||||
(filterScopeMap[filterField].checked || []).forEach(chartId => {
|
||||
(filterScopeMap[filterField]?.checked || []).forEach(chartId => {
|
||||
checkedChartIdSet.add(`${chartId}:${filterField}`);
|
||||
});
|
||||
});
|
||||
const checked = [...checkedChartIdSet];
|
||||
const expanded = filterScopeMap[key]
|
||||
? filterScopeMap[key].expanded
|
||||
? filterScopeMap[key].expanded || []
|
||||
: getFilterScopeParentNodes(nodes, 1);
|
||||
|
||||
return {
|
||||
@@ -16,7 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import dropOverflowsParent from 'src/dashboard/util/dropOverflowsParent';
|
||||
// Layout type not directly used in tests - using object shapes for test data
|
||||
import dropOverflowsParent, {
|
||||
type DropResult,
|
||||
} from 'src/dashboard/util/dropOverflowsParent';
|
||||
import { NEW_COMPONENTS_SOURCE_ID } from 'src/dashboard/util/constants';
|
||||
import {
|
||||
CHART_TYPE,
|
||||
@@ -28,7 +31,7 @@ import {
|
||||
|
||||
describe('dropOverflowsParent', () => {
|
||||
it('returns true if a parent does NOT have adequate width for child', () => {
|
||||
const dropResult = {
|
||||
const dropResult: DropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'a' },
|
||||
dragging: { id: 'z' },
|
||||
@@ -56,11 +59,11 @@ describe('dropOverflowsParent', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(true);
|
||||
expect(dropOverflowsParent(dropResult, layout as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if a parent DOES have adequate width for child', () => {
|
||||
const dropResult = {
|
||||
const dropResult: DropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'a' },
|
||||
dragging: { id: 'z' },
|
||||
@@ -88,11 +91,11 @@ describe('dropOverflowsParent', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
|
||||
expect(dropOverflowsParent(dropResult, layout as any)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if a child CAN shrink to available parent space', () => {
|
||||
const dropResult = {
|
||||
const dropResult: DropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'a' },
|
||||
dragging: { id: 'z' },
|
||||
@@ -120,11 +123,11 @@ describe('dropOverflowsParent', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
|
||||
expect(dropOverflowsParent(dropResult, layout as any)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if a child CANNOT shrink to available parent space', () => {
|
||||
const dropResult = {
|
||||
const dropResult: DropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'a' },
|
||||
dragging: { id: 'b' },
|
||||
@@ -153,11 +156,11 @@ describe('dropOverflowsParent', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(true);
|
||||
expect(dropOverflowsParent(dropResult, layout as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true if a column has children that CANNOT shrink to available parent space', () => {
|
||||
const dropResult = {
|
||||
const dropResult: DropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'destination' },
|
||||
dragging: { id: 'dragging' },
|
||||
@@ -191,18 +194,18 @@ describe('dropOverflowsParent', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(true);
|
||||
expect(dropOverflowsParent(dropResult, layout as any)).toBe(true);
|
||||
// remove children
|
||||
expect(
|
||||
dropOverflowsParent(dropResult, {
|
||||
...layout,
|
||||
dragging: { ...layout.dragging, children: [] },
|
||||
}),
|
||||
dragging: { ...layout.dragging, children: [] } as any,
|
||||
} as any),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should work with new components that are not in the layout', () => {
|
||||
const dropResult = {
|
||||
const dropResult: DropResult = {
|
||||
source: { id: NEW_COMPONENTS_SOURCE_ID },
|
||||
destination: { id: 'a' },
|
||||
dragging: { type: CHART_TYPE },
|
||||
@@ -212,15 +215,15 @@ describe('dropOverflowsParent', () => {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: ROW_TYPE,
|
||||
children: [],
|
||||
children: [] as any,
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
|
||||
expect(dropOverflowsParent(dropResult, layout as any)).toBe(false);
|
||||
});
|
||||
|
||||
it('source/destination without widths should not overflow parent', () => {
|
||||
const dropResult = {
|
||||
const dropResult: DropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'tab' },
|
||||
dragging: { id: 'header' },
|
||||
@@ -237,6 +240,6 @@ describe('dropOverflowsParent', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
|
||||
expect(dropOverflowsParent(dropResult, layout as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -16,9 +16,22 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import type { ComponentType, Layout } from 'src/dashboard/types';
|
||||
import getComponentWidthFromDrop from './getComponentWidthFromDrop';
|
||||
|
||||
export default function doesChildOverflowParent(dropResult, layout) {
|
||||
export interface DropResult {
|
||||
source: { id: string };
|
||||
destination: { id: string };
|
||||
dragging: {
|
||||
id?: string;
|
||||
type?: ComponentType;
|
||||
};
|
||||
}
|
||||
|
||||
export default function doesChildOverflowParent(
|
||||
dropResult: DropResult,
|
||||
layout: Layout,
|
||||
): boolean {
|
||||
const childWidth = getComponentWidthFromDrop({ dropResult, layout });
|
||||
return typeof childWidth === 'number' && childWidth < 0;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user