Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,403 changes: 1,216 additions & 1,187 deletions proto/gen/rill/runtime/v1/api.pb.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions proto/gen/rill/runtime/v1/api.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions proto/gen/rill/runtime/v1/runtime.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,8 @@ paths:
type: string
frontendUrl:
type: string
theme:
type: string
description: |-
Request message for RuntimeService.EditInstance.
See message Instance for field descriptions.
Expand Down Expand Up @@ -4615,6 +4617,8 @@ definitions:
type: string
frontendUrl:
type: string
theme:
type: string
description: |-
Request message for RuntimeService.CreateInstance.
See message Instance for field descriptions.
Expand Down Expand Up @@ -5149,6 +5153,8 @@ definitions:
type: string
frontendUrl:
type: string
theme:
type: string
description: |-
Instance represents a single data project, meaning one set of code artifacts,
one connection to an OLAP datastore (DuckDB, Druid), and one catalog of related
Expand Down
3 changes: 3 additions & 0 deletions proto/rill/runtime/v1/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ message Instance {
map<string, string> annotations = 14;
string ai_instructions = 23;
string frontend_url = 24;
string theme = 26;
}

message Connector {
Expand Down Expand Up @@ -397,6 +398,7 @@ message CreateInstanceRequest {
map<string, string> variables = 7;
map<string, string> annotations = 9;
string frontend_url = 18;
string theme = 19;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove the option here? Since the field gets populated from rill.yaml, it should not be possible to pass it over the APIs CreateInstance or EditInstance (because the YAML parser runs continuously and would quickly overwrite anything passed through the API)

}

// Response message for RuntimeService.CreateInstance
Expand Down Expand Up @@ -425,6 +427,7 @@ message EditInstanceRequest {
map<string, string> variables = 15;
map<string, string> annotations = 10;
optional string frontend_url = 19;
optional string theme = 20;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above

}

// Response message for RuntimeService.EditInstance
Expand Down
2 changes: 2 additions & 0 deletions runtime/drivers/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type Instance struct {
AIInstructions string `db:"ai_instructions"`
// FrontendURL is the URL of the web interface.
FrontendURL string `db:"frontend_url"`
// Theme is the name of the theme resource to use for AI-generated charts.
Theme string `db:"theme"`
}

// InstanceConfig contains dynamic configuration for an instance.
Expand Down
2 changes: 2 additions & 0 deletions runtime/drivers/sqlite/migrations/0038.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE instances ADD COLUMN theme TEXT NOT NULL DEFAULT '';

14 changes: 10 additions & 4 deletions runtime/drivers/sqlite/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ func (c *connection) findInstances(_ context.Context, whereClause string, args .
annotations,
public_paths,
ai_instructions,
frontend_url
frontend_url,
theme
FROM instances %s ORDER BY id
`, whereClause)

Expand Down Expand Up @@ -92,6 +93,7 @@ func (c *connection) findInstances(_ context.Context, whereClause string, args .
&publicPaths,
&i.AIInstructions,
&i.FrontendURL,
&i.Theme,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -225,9 +227,10 @@ func (c *connection) CreateInstance(_ context.Context, inst *drivers.Instance) e
annotations,
public_paths,
ai_instructions,
frontend_url
frontend_url,
theme
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)
`,
inst.ID,
inst.Environment,
Expand All @@ -250,6 +253,7 @@ func (c *connection) CreateInstance(_ context.Context, inst *drivers.Instance) e
publicPaths,
inst.AIInstructions,
inst.FrontendURL,
inst.Theme,
)
if err != nil {
return err
Expand Down Expand Up @@ -325,7 +329,8 @@ func (c *connection) EditInstance(_ context.Context, inst *drivers.Instance) err
annotations = $17,
public_paths = $18,
ai_instructions = $19,
frontend_url = $20
frontend_url = $20,
theme = $21
WHERE id = $1
`,
inst.ID,
Expand All @@ -348,6 +353,7 @@ func (c *connection) EditInstance(_ context.Context, inst *drivers.Instance) err
publicPaths,
inst.AIInstructions,
inst.FrontendURL,
inst.Theme,
)
if err != nil {
return err
Expand Down
4 changes: 4 additions & 0 deletions runtime/parser/parse_canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ func (p *Parser) parseCanvas(node *Node) error {
if err != nil {
return err
}
// Fallback to top-level theme from rill.yaml if no local theme or default theme is set
if themeName == "" && themeSpec == nil && p.RillYAML != nil && p.RillYAML.Theme != "" {
themeName = p.RillYAML.Theme
}
if themeName != "" && themeSpec == nil {
node.Refs = append(node.Refs, ResourceName{Kind: ResourceKindTheme, Name: themeName})
}
Expand Down
4 changes: 4 additions & 0 deletions runtime/parser/parse_explore.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ func (p *Parser) parseExplore(node *Node) error {
if err != nil {
return err
}
// Fallback to top-level theme from rill.yaml if no local theme or default theme is set
if themeName == "" && themeSpec == nil && p.RillYAML != nil && p.RillYAML.Theme != "" {
themeName = p.RillYAML.Theme
}
if themeName != "" && themeSpec == nil {
node.Refs = append(node.Refs, ResourceName{Kind: ResourceKindTheme, Name: themeName})
}
Expand Down
4 changes: 4 additions & 0 deletions runtime/parser/parse_rillyaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type RillYAML struct {
AIInstructions string
OLAPConnector string
AIConnector string
Theme string
Connectors []*ConnectorDef
Variables []*VariableDef
Defaults map[ResourceKind]yaml.Node
Expand Down Expand Up @@ -61,6 +62,8 @@ type rillYAML struct {
AIInstructions string `yaml:"ai_instructions"`
// Connector to use for the AI service
AIConnector string `yaml:"ai_connector"`
// Theme resource name to use for AI-generated charts
Theme string `yaml:"theme"`
// The project's default OLAP connector to use (can be overridden in the individual resources)
OLAPConnector string `yaml:"olap_connector"`
// Connectors required by the project
Expand Down Expand Up @@ -303,6 +306,7 @@ func (p *Parser) parseRillYAML(ctx context.Context, path string) error {
Description: tmp.Description,
AIInstructions: tmp.AIInstructions,
AIConnector: tmp.AIConnector,
Theme: tmp.Theme,
OLAPConnector: tmp.OLAPConnector,
Connectors: make([]*ConnectorDef, len(tmp.Connectors)),
Variables: make([]*VariableDef, len(vars)),
Expand Down
82 changes: 68 additions & 14 deletions runtime/reconcilers/project_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,34 +297,88 @@ func (r *ProjectParserReconciler) reconcileParser(ctx context.Context, inst *dri
}
}

// Set an error without returning to mark if there are parse errors (if not, force error to nil in case there previously were parse errors)
var parseErrsErr error
if len(parser.Errors) > 0 {
parseErrsErr = ErrParserHasParseErrors
}
err = r.C.UpdateError(ctx, self.Meta.Name, parseErrsErr)
if err != nil {
return err
}
Comment on lines -300 to -308
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think moving this is safe – it will prevent parse errors from showing up in Rill Developer


// If RillYAML is missing, don't reconcile anything
if parser.RillYAML == nil {
// Set an error without returning to mark if there are parse errors
var parseErrsErr error
if len(parser.Errors) > 0 {
parseErrsErr = ErrParserHasParseErrors
}
err = r.C.UpdateError(ctx, self.Meta.Name, parseErrsErr)
if err != nil {
return err
}
return parseErrsErr
}

// Check if theme referenced theme changed
themeChanged := false
if parser.RillYAML.Theme != "" && diff != nil {
referencedTheme := parserpkg.ResourceName{Kind: parserpkg.ResourceKindTheme, Name: parser.RillYAML.Theme}
for _, n := range diff.Added {
if n.Normalized() == referencedTheme.Normalized() {
themeChanged = true
break
}
}
if !themeChanged {
for _, n := range diff.Modified {
if n.Normalized() == referencedTheme.Normalized() {
themeChanged = true
break
}
}
}
if !themeChanged {
for _, n := range diff.Deleted {
if n.Normalized() == referencedTheme.Normalized() {
themeChanged = true
break
}
}
}
}
Comment on lines +314 to +340
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed? It does a full re-parse of the project when rill.yaml is changed, so I don't think any further changes here should be necessary (unless I'm missing something?)


// not setting restartController=true when diff is actually nil prevents infinite restarts
updateConfig := diff == nil || diff.ModifiedDotEnv || diff.Reloaded
updateConfig := diff == nil || diff.ModifiedDotEnv || diff.Reloaded || themeChanged
if updateConfig {
restartController := diff != nil
err := r.reconcileProjectConfig(ctx, parser, restartController)
if err != nil {
return err
}
if restartController {
// Check if this error already exists to avoid duplicates
errMsg := err.Error()
alreadyExists := false
for _, e := range parser.Errors {
if e.FilePath == "/rill.yaml" && e.Message == errMsg {
alreadyExists = true
break
}
}
if !alreadyExists {
parser.Errors = append(parser.Errors, &runtimev1.ParseError{
FilePath: "/rill.yaml",
Message: errMsg,
})
pp.State.ParseErrors = parser.Errors
if err := r.C.UpdateState(ctx, self.Meta.Name, self); err != nil {
return err
}
}
} else if restartController {
return nil
}
}

// Set an error without returning to mark if there are parse errors (if not, force error to nil in case there previously were parse errors)
var parseErrsErr error
if len(parser.Errors) > 0 {
parseErrsErr = ErrParserHasParseErrors
}
err = r.C.UpdateError(ctx, self.Meta.Name, parseErrsErr)
if err != nil {
return err
}

// Reconcile resources.
// The lock serves to delay the controller from triggering reconciliation until all resources have been updated.
// By delaying reconciliation until all resources have been updated, we don't need to worry about making changes in DAG order here.
Expand Down
11 changes: 11 additions & 0 deletions runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ func (r *Runtime) UpdateInstanceWithRillYAML(ctx context.Context, instanceID str
inst.PublicPaths = rillYAML.PublicPaths
inst.AIInstructions = rillYAML.AIInstructions
inst.ProjectAIConnector = rillYAML.AIConnector

// Validate theme references an existing theme resource
if rillYAML.Theme != "" {
// Check if theme exists in parsed resources using normalized name (case-insensitive)
themeName := parser.ResourceName{Kind: parser.ResourceKindTheme, Name: rillYAML.Theme}
if _, ok := p.Resources[themeName.Normalized()]; !ok {
return fmt.Errorf("theme references theme %q which does not exist", rillYAML.Theme)
}
}

inst.Theme = rillYAML.Theme
Comment on lines 186 to +197
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just assign the expressed theme without doing validation. This is not a safe place to do validation – validation has to be done when parsing or when reconciling/consuming the value.

It'll be difficult to validate this at parse time because rill.yaml is parsed before any other file is parsed, so it won't know if the theme exists or not. Due to unpredictable parse order, we generally don't validate references in the parser – we do that in reconciles. But for AI themes, there is no resource to reconcile.

So I suggest you validate this when consuming the setting in the AI chat UI. I.e. show an error if the theme doesn't exist.

return r.EditInstance(ctx, inst, restartController)
}

Expand Down
3 changes: 3 additions & 0 deletions runtime/server/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (s *Server) CreateInstance(ctx context.Context, req *runtimev1.CreateInstan
Variables: req.Variables,
Annotations: req.Annotations,
FrontendURL: req.FrontendUrl,
Theme: req.Theme,
}

err := s.runtime.CreateInstance(ctx, inst)
Expand Down Expand Up @@ -187,6 +188,7 @@ func (s *Server) EditInstance(ctx context.Context, req *runtimev1.EditInstanceRe
Annotations: annotations,
AIInstructions: oldInst.AIInstructions,
FrontendURL: valOrDefault(req.FrontendUrl, oldInst.FrontendURL),
Theme: valOrDefault(req.Theme, oldInst.Theme),
}

err = s.runtime.EditInstance(ctx, inst, true)
Expand Down Expand Up @@ -312,6 +314,7 @@ func instanceToPB(inst *drivers.Instance, featureFlags map[string]bool, sensitiv
FeatureFlags: featureFlags,
AiInstructions: inst.AIInstructions,
FrontendUrl: inst.FrontendURL,
Theme: inst.Theme,
}

if sensitive {
Expand Down
Loading
Loading