Skip to content

Commit c93af28

Browse files
authored
Merge branch 'main' into fix/pass-system-team-uri
2 parents 08024ee + fe1b364 commit c93af28

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+811
-3721
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Notable Changes
66

77
### CLI
8+
* Skip non-exportable objects (e.g., `MLFLOW_EXPERIMENT`) during `workspace export-dir` instead of failing ([#4081](https://github.com/databricks/cli/issues/4081))
89

910
### Bundles
1011

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11

22
>>> [CLI] workspace export-dir /test-dir [TEST_TMP_DIR]/export
33
Exporting files from /test-dir
4-
Warning: /test-dir/file.py (skipped; file too large)
5-
6-
The following files were skipped because they exceed the maximum size limit:
7-
- /test-dir/file.py (skipped; file too large)
8-
4+
/test-dir/file.py (skipped; file too large)
95
Export complete

acceptance/cmd/workspace/export-dir-skip-experiments/out.test.toml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
>>> [CLI] workspace export-dir /test-dir [TEST_TMP_DIR]/export
3+
Exporting files from /test-dir
4+
/test-dir/experiment (skipped; cannot export MLFLOW_EXPERIMENT)
5+
Export complete
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mkdir -p "$TEST_TMP_DIR/export"
2+
trace $CLI workspace export-dir /test-dir "$TEST_TMP_DIR/export"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Local = true
2+
Cloud = false
3+
4+
[Env]
5+
MSYS_NO_PATHCONV = "1"
6+
7+
[[Server]]
8+
Pattern = "GET /api/2.0/workspace/list"
9+
Response.Body = '''
10+
{
11+
"objects": [
12+
{
13+
"path": "/test-dir/experiment",
14+
"object_type": "MLFLOW_EXPERIMENT",
15+
"object_id": 125
16+
}
17+
]
18+
}
19+
'''
20+
21+
[[Server]]
22+
Pattern = "GET /api/2.0/workspace/get-status"
23+
Response.Body = '''
24+
{
25+
"path": "/test-dir",
26+
"object_type": "DIRECTORY",
27+
"object_id": 123
28+
}
29+
'''

bundle/direct/dresources/app.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package dresources
33
import (
44
"context"
55
"fmt"
6+
"time"
67

78
"github.com/databricks/cli/bundle/config/resources"
89
"github.com/databricks/cli/bundle/deployplan"
910
"github.com/databricks/cli/libs/log"
1011
"github.com/databricks/databricks-sdk-go"
12+
"github.com/databricks/databricks-sdk-go/apierr"
1113
"github.com/databricks/databricks-sdk-go/retries"
1214
"github.com/databricks/databricks-sdk-go/service/apps"
1315
)
@@ -61,7 +63,10 @@ func (r *ResourceApp) DoUpdate(ctx context.Context, id string, config *apps.App,
6163

6264
func (r *ResourceApp) DoDelete(ctx context.Context, id string) error {
6365
_, err := r.client.Apps.DeleteByName(ctx, id)
64-
return err
66+
if err != nil {
67+
return err
68+
}
69+
return r.waitForDeletion(ctx, id)
6570
}
6671

6772
func (*ResourceApp) FieldTriggers(_ bool) map[string]deployplan.ActionType {
@@ -74,6 +79,34 @@ func (r *ResourceApp) WaitAfterCreate(ctx context.Context, config *apps.App) (*a
7479
return r.waitForApp(ctx, r.client, config.Name)
7580
}
7681

82+
func (r *ResourceApp) waitForDeletion(ctx context.Context, name string) error {
83+
retrier := retries.New[struct{}](retries.WithTimeout(10*time.Minute), retries.WithRetryFunc(shouldRetry))
84+
_, err := retrier.Run(ctx, func(ctx context.Context) (*struct{}, error) {
85+
app, err := r.client.Apps.GetByName(ctx, name)
86+
if err != nil {
87+
if apierr.IsMissing(err) {
88+
return nil, nil
89+
}
90+
return nil, retries.Halt(err)
91+
}
92+
93+
if app.ComputeStatus == nil {
94+
return nil, retries.Continues("waiting for compute status")
95+
}
96+
97+
switch app.ComputeStatus.State {
98+
case apps.ComputeStateDeleting:
99+
return nil, retries.Continues("app is deleting")
100+
case apps.ComputeStateActive, apps.ComputeStateStopped, apps.ComputeStateError:
101+
err := fmt.Errorf("app %s was not deleted, current state: %s", name, app.ComputeStatus.State)
102+
return nil, retries.Halt(err)
103+
default:
104+
return nil, retries.Continues(fmt.Sprintf("app is in %s state", app.ComputeStatus.State))
105+
}
106+
})
107+
return err
108+
}
109+
77110
// waitForApp waits for the app to reach the target state. The target state is either ACTIVE or STOPPED.
78111
// Apps with no_compute set to true will reach the STOPPED state, otherwise they will reach the ACTIVE state.
79112
// We can't use the default waiter from SDK because it only waits on ACTIVE state but we need also STOPPED state.

cmd/workspace/workspace/export_dir.go

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package workspace
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"io"
78
"io/fs"
89
"net/http"
@@ -24,7 +25,6 @@ type exportDirOptions struct {
2425
sourceDir string
2526
targetDir string
2627
overwrite bool
27-
warnings []string
2828
}
2929

3030
// isFileSizeError checks if the error is due to file size limits.
@@ -51,6 +51,26 @@ func isFileSizeError(err error) bool {
5151
return false
5252
}
5353

54+
// Object types that cannot be exported via the workspace export API.
55+
// These will be skipped with a warning during export-dir.
56+
var nonExportableTypes = []workspace.ObjectType{
57+
workspace.ObjectTypeLibrary,
58+
workspace.ObjectTypeDashboard,
59+
workspace.ObjectTypeRepo,
60+
// MLFLOW_EXPERIMENT is not defined as a constant in the SDK
61+
workspace.ObjectType("MLFLOW_EXPERIMENT"),
62+
}
63+
64+
// isNonExportable checks if an object type cannot be exported.
65+
func isNonExportable(objectType workspace.ObjectType) bool {
66+
for _, t := range nonExportableTypes {
67+
if objectType == t {
68+
return true
69+
}
70+
}
71+
return false
72+
}
73+
5474
// The callback function exports the file specified at relPath. This function is
5575
// meant to be used in conjunction with fs.WalkDir
5676
func (opts *exportDirOptions) callback(ctx context.Context, workspaceFiler filer.Filer) func(string, fs.DirEntry, error) error {
@@ -77,6 +97,13 @@ func (opts *exportDirOptions) callback(ctx context.Context, workspaceFiler filer
7797
return err
7898
}
7999
objectInfo := info.Sys().(workspace.ObjectInfo)
100+
101+
// Skip non-exportable objects (e.g., MLFLOW_EXPERIMENT, LIBRARY)
102+
if isNonExportable(objectInfo.ObjectType) {
103+
cmdio.LogString(ctx, fmt.Sprintf("%s (skipped; cannot export %s)", sourcePath, objectInfo.ObjectType))
104+
return nil
105+
}
106+
80107
targetPath += notebook.GetExtensionByLanguage(&objectInfo)
81108

82109
// Skip file if a file already exists in path.
@@ -92,9 +119,7 @@ func (opts *exportDirOptions) callback(ctx context.Context, workspaceFiler filer
92119
if err != nil {
93120
// Check if this is a file size limit error
94121
if isFileSizeError(err) {
95-
warning := sourcePath + " (skipped; file too large)"
96-
cmdio.LogString(ctx, "Warning: "+warning)
97-
opts.warnings = append(opts.warnings, warning)
122+
cmdio.LogString(ctx, sourcePath+" (skipped; file too large)")
98123
return nil
99124
}
100125
return err
@@ -140,7 +165,6 @@ func newExportDir() *cobra.Command {
140165
w := cmdctx.WorkspaceClient(ctx)
141166
opts.sourceDir = args[0]
142167
opts.targetDir = args[1]
143-
opts.warnings = []string{}
144168

145169
// Initialize a filer and a file system on the source directory
146170
workspaceFiler, err := filer.NewWorkspaceFilesClient(w, opts.sourceDir)
@@ -159,16 +183,6 @@ func newExportDir() *cobra.Command {
159183
return err
160184
}
161185

162-
// Print all warnings at the end if any were collected
163-
if len(opts.warnings) > 0 {
164-
cmdio.LogString(ctx, "")
165-
cmdio.LogString(ctx, "The following files were skipped because they exceed the maximum size limit:")
166-
for _, warning := range opts.warnings {
167-
cmdio.LogString(ctx, " - "+warning)
168-
}
169-
cmdio.LogString(ctx, "")
170-
}
171-
172186
return cmdio.RenderWithTemplate(ctx, newExportCompletedEvent(opts.targetDir), "", "Export complete\n")
173187
}
174188

experimental/apps-mcp/templates/appkit/template/{{.project_name}}/CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const querySchemas = {
3838

3939
```typescript
4040
// client/src/App.tsx
41-
import { BarChart } from '@databricks/app-kit-ui/react';
41+
import { BarChart } from '@databricks/appkit-ui/react';
4242

4343
<BarChart queryKey="my_data" parameters={{}} />
4444
```
@@ -73,7 +73,7 @@ See the databricks experimental apps-mcp tools validate instead of running these
7373
**IMPORTANT**: Read the relevant docs below before implementing features. They contain critical information about common pitfalls (e.g., SQL numeric type handling, schema definitions, Radix UI constraints).
7474

7575
- [SQL Queries](docs/sql-queries.md) - query files, schemas, type handling, parameterization
76-
- [App Kit SDK](docs/app-kit-sdk.md) - TypeScript imports, server setup, useAnalyticsQuery hook
76+
- [App Kit SDK](docs/appkit-sdk.md) - TypeScript imports, server setup, useAnalyticsQuery hook
7777
- [Frontend](docs/frontend.md) - visualization components, styling, layout, Radix constraints
7878
- [tRPC](docs/trpc.md) - custom endpoints for non-SQL operations (mutations, Databricks APIs)
7979
- [Testing](docs/testing.md) - vitest unit tests, Playwright smoke/E2E tests

experimental/apps-mcp/templates/appkit/template/{{.project_name}}/client/src/App.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
import { useAnalyticsQuery, AreaChart, LineChart, RadarChart } from '@databricks/app-kit-ui/react';
2-
import { sql } from "@databricks/app-kit-ui/js";
1+
import {
2+
useAnalyticsQuery,
3+
AreaChart,
4+
LineChart,
5+
RadarChart,
6+
Card,
7+
CardContent,
8+
CardHeader,
9+
CardTitle,
10+
Skeleton,
11+
Label,
12+
Select,
13+
SelectContent,
14+
SelectItem,
15+
SelectTrigger,
16+
SelectValue,
17+
} from '@databricks/appkit-ui/react';
18+
import { sql } from "@databricks/appkit-ui/js";
319
import { Line, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
4-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5-
import { Skeleton } from '@/components/ui/skeleton';
6-
import { Label } from '@/components/ui/label';
7-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
820
import { trpc } from './lib/trpc';
921
import { useState, useEffect } from 'react';
1022

@@ -98,8 +110,8 @@ function App() {
98110
{health && (
99111
<div className="space-y-2">
100112
<div className="flex items-center gap-2">
101-
<div className="w-2 h-2 rounded-full bg-[hsl(var(--success))] animate-pulse"></div>
102-
<div className="text-lg font-semibold text-[hsl(var(--success))]">{health.status.toUpperCase()}</div>
113+
<div className="w-2 h-2 rounded-full bg-success animate-pulse"></div>
114+
<div className="text-lg font-semibold text-success">{health.status.toUpperCase()}</div>
103115
</div>
104116
<div className="text-sm text-muted-foreground">
105117
Last checked: {new Date(health.timestamp).toLocaleString()}

0 commit comments

Comments
 (0)