mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-09 15:38:08 -05:00
feat(tools/looker): New Looker tools for dashboards (#1118)
* get_dashboards * make_dashboard * add_dashboard_element
This commit is contained in:
@@ -61,6 +61,8 @@ import (
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestorequerycollection"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestorevalidaterules"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/http"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardelement"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetdashboards"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetdimensions"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetexplores"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetfilters"
|
||||
@@ -68,6 +70,7 @@ import (
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetmeasures"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetmodels"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetparameters"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookermakedashboard"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookermakelook"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerquery"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerquerysql"
|
||||
|
||||
@@ -1290,7 +1290,7 @@ func TestPrebuiltTools(t *testing.T) {
|
||||
wantToolset: server.ToolsetConfigs{
|
||||
"looker-tools": tools.ToolsetConfig{
|
||||
Name: "looker-tools",
|
||||
ToolNames: []string{"get_models", "get_explores", "get_dimensions", "get_measures", "get_filters", "get_parameters", "query", "query_sql", "query_url", "get_looks", "run_look", "make_look"},
|
||||
ToolNames: []string{"get_models", "get_explores", "get_dimensions", "get_measures", "get_filters", "get_parameters", "query", "query_sql", "query_url", "get_looks", "run_look", "make_look", "get_dashboards", "make_dashboard", "add_dashboard_element"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "looker-add-dashboard-element"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
"looker-add-dashboard-element" generates a Looker look in the users personal folder in
|
||||
Looker
|
||||
aliases:
|
||||
- /resources/tools/looker-add-dashboard-element
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
The `looker-add-dashboard-element` creates a dashboard element
|
||||
in the given dashboard.
|
||||
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [looker](../../sources/looker.md)
|
||||
|
||||
`looker-add-dashboard-element` takes eleven parameters:
|
||||
|
||||
1. the `model`
|
||||
2. the `explore`
|
||||
3. the `fields` list
|
||||
4. an optional set of `filters`
|
||||
5. an optional set of `pivots`
|
||||
6. an optional set of `sorts`
|
||||
7. an optional `limit`
|
||||
8. an optional `tz`
|
||||
9. an optional `vis_config`
|
||||
10. the `title`
|
||||
11. the `dashboard_id`
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
add_dashboard_element:
|
||||
kind: looker-add-dashboard-element
|
||||
source: looker-source
|
||||
description: |
|
||||
add_dashboard_element Tool
|
||||
|
||||
This tool creates a new tile in a Looker dashboard using
|
||||
the query parameters and the vis_config specified.
|
||||
|
||||
Most of the parameters are the same as the query_url
|
||||
tool. In addition, there is a title that may be provided.
|
||||
The dashboard_id must be specified. That is obtained
|
||||
from calling make_dashboard.
|
||||
|
||||
This tool can be called many times for one dashboard_id
|
||||
and the resulting tiles will be added in order.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
|
||||
| kind | string | true | Must be "looker-add-dashboard-element" |
|
||||
| source | string | true | Name of the source the SQL should execute on. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
60
docs/en/resources/tools/looker/looker-get-dashboards.md
Normal file
60
docs/en/resources/tools/looker/looker-get-dashboards.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: "looker-get-dashboards"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
"looker-get-dashboards" searches for saved Looks in a Looker
|
||||
source.
|
||||
aliases:
|
||||
- /resources/tools/looker-get-dashboards
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
The `looker-get-dashboards` tool searches for a saved Dashboard by
|
||||
name or description.
|
||||
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [looker](../../sources/looker.md)
|
||||
|
||||
`looker-get-dashboards` takes four parameters, the `title`, `desc`, `limit`
|
||||
and `offset`.
|
||||
|
||||
Title and description use SQL style wildcards and are case insensitive.
|
||||
|
||||
Limit and offset are used to page through a larger set of matches and
|
||||
default to 100 and 0.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
get_dashboards:
|
||||
kind: looker-get-dashboards
|
||||
source: looker-source
|
||||
description: |
|
||||
get_dashboards Tool
|
||||
|
||||
This tool is used to search for saved dashboards in a Looker instance.
|
||||
String search params use case-insensitive matching. String search
|
||||
params can contain % and '_' as SQL LIKE pattern match wildcard
|
||||
expressions. example="dan%" will match "danger" and "Danzig" but
|
||||
not "David" example="D_m%" will match "Damage" and "dump".
|
||||
|
||||
Most search params can accept "IS NULL" and "NOT NULL" as special
|
||||
expressions to match or exclude (respectively) rows where the
|
||||
column is null.
|
||||
|
||||
The limit and offset are used to paginate the results.
|
||||
|
||||
The result of the get_dashboards tool is a list of json objects.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
|
||||
| kind | string | true | Must be "looker-get-dashboards" |
|
||||
| source | string | true | Name of the source the SQL should execute on. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
53
docs/en/resources/tools/looker/looker-make-dashboard.md
Normal file
53
docs/en/resources/tools/looker/looker-make-dashboard.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: "looker-make-dashboard"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
"looker-make-dashboard" generates a Looker dashboard in the users personal folder in
|
||||
Looker
|
||||
aliases:
|
||||
- /resources/tools/looker-make-dashboard
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
The `looker-make-dashboard` creates a dashboard in the user's
|
||||
Looker personal folder.
|
||||
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [looker](../../sources/looker.md)
|
||||
|
||||
`looker-make-dashboard` takes one parameter:
|
||||
|
||||
1. the `title`
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
make_dashboard:
|
||||
kind: looker-make-dashboard
|
||||
source: looker-source
|
||||
description: |
|
||||
make_dashboard Tool
|
||||
|
||||
This tool creates a new dashboard in Looker. The dashboard is
|
||||
initially empty and the add_dashboard_element tool is used to
|
||||
add content to the dashboard.
|
||||
|
||||
The newly created dashboard will be created in the user's
|
||||
personal folder in looker. The dashboard name must be unique.
|
||||
|
||||
The result is a json document with a link to the newly
|
||||
created dashboard and the id of the dashboard. Use the id
|
||||
when calling add_dashboard_element.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
|
||||
| kind | string | true | Must be "looker-make-dashboard" |
|
||||
| source | string | true | Name of the source the SQL should execute on. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
@@ -18,7 +18,7 @@ It's compatible with the following sources:
|
||||
|
||||
- [looker](../../sources/looker.md)
|
||||
|
||||
`looker-make-look` takes eight parameters:
|
||||
`looker-make-look` takes eleven parameters:
|
||||
|
||||
1. the `model`
|
||||
2. the `explore`
|
||||
|
||||
@@ -17,7 +17,7 @@ It's compatible with the following sources:
|
||||
|
||||
- [looker](../../sources/looker.md)
|
||||
|
||||
`looker-query-url` takes eight parameters:
|
||||
`looker-query-url` takes nine parameters:
|
||||
|
||||
1. the `model`
|
||||
2. the `explore`
|
||||
@@ -46,8 +46,121 @@ tools:
|
||||
parameter.
|
||||
|
||||
The vis_config is optional. If provided, it will be used to
|
||||
control the default visualization for the query. These are
|
||||
some sample vis_config settings.
|
||||
control the default visualization for the query. Here are
|
||||
some notes on making visualizations.
|
||||
|
||||
### Cartesian Charts (Area, Bar, Column, Line, Scatter)
|
||||
|
||||
These chart types share a large number of configuration options.
|
||||
|
||||
**General**
|
||||
* `type`: The type of visualization (`looker_area`, `looker_bar`, `looker_column`, `looker_line`, `looker_scatter`).
|
||||
* `series_types`: Override the chart type for individual series.
|
||||
* `show_view_names`: Display view names in labels and tooltips (`true`/`false`).
|
||||
* `series_labels`: Provide custom names for series.
|
||||
|
||||
**Styling & Colors**
|
||||
* `colors`: An array of color values to be used for the chart series.
|
||||
* `series_colors`: A mapping of series names to specific color values.
|
||||
* `color_application`: Advanced controls for color palette application (collection, palette, reverse, etc.).
|
||||
* `font_size`: Font size for labels (e.g., '12px').
|
||||
|
||||
**Legend**
|
||||
* `hide_legend`: Show or hide the chart legend (`true`/`false`).
|
||||
* `legend_position`: Placement of the legend (`'center'`, `'left'`, `'right'`).
|
||||
|
||||
**Axes**
|
||||
* `swap_axes`: Swap the X and Y axes (`true`/`false`).
|
||||
* `x_axis_scale`: Scale of the x-axis (`'auto'`, `'ordinal'`, `'linear'`, `'time'`).
|
||||
* `x_axis_reversed`, `y_axis_reversed`: Reverse the direction of an axis (`true`/`false`).
|
||||
* `x_axis_gridlines`, `y_axis_gridlines`: Display gridlines for an axis (`true`/`false`).
|
||||
* `show_x_axis_label`, `show_y_axis_label`: Show or hide the axis title (`true`/`false`).
|
||||
* `show_x_axis_ticks`, `show_y_axis_ticks`: Show or hide axis tick marks (`true`/`false`).
|
||||
* `x_axis_label`, `y_axis_label`: Set a custom title for an axis.
|
||||
* `x_axis_datetime_label`: A format string for datetime labels on the x-axis (e.g., `'%Y-%m'`).
|
||||
* `x_padding_left`, `x_padding_right`: Adjust padding on the ends of the x-axis.
|
||||
* `x_axis_label_rotation`, `x_axis_label_rotation_bar`: Set rotation for x-axis labels.
|
||||
* `x_axis_zoom`, `y_axis_zoom`: Enable zooming on an axis (`true`/`false`).
|
||||
* `y_axes`: An array of configuration objects for multiple y-axes.
|
||||
|
||||
**Data & Series**
|
||||
* `stacking`: How to stack series (`''` for none, `'normal'`, `'percent'`).
|
||||
* `ordering`: Order of series in a stack (`'none'`, etc.).
|
||||
* `limit_displayed_rows`: Enable or disable limiting the number of rows displayed (`true`/`false`).
|
||||
* `limit_displayed_rows_values`: Configuration for the row limit (e.g., `{ "first_last": "first", "show_hide": "show", "num_rows": 10 }`).
|
||||
* `discontinuous_nulls`: How to render null values in line charts (`true`/`false`).
|
||||
* `point_style`: Style for points on line and area charts (`'none'`, `'circle'`, `'circle_outline'`).
|
||||
* `series_point_styles`: Override point styles for individual series.
|
||||
* `interpolation`: Line interpolation style (`'linear'`, `'monotone'`, `'step'`, etc.).
|
||||
* `show_value_labels`: Display values on data points (`true`/`false`).
|
||||
* `label_value_format`: A format string for value labels.
|
||||
* `show_totals_labels`: Display total labels on stacked charts (`true`/`false`).
|
||||
* `totals_color`: Color for total labels.
|
||||
* `show_silhouette`: Display a "silhouette" of hidden series in stacked charts (`true`/`false`).
|
||||
* `hidden_series`: An array of series names to hide from the visualization.
|
||||
|
||||
**Scatter/Bubble Specific**
|
||||
* `size_by_field`: The field used to determine the size of bubbles.
|
||||
* `color_by_field`: The field used to determine the color of bubbles.
|
||||
* `plot_size_by_field`: Whether to display the size-by field in the legend.
|
||||
* `cluster_points`: Group nearby points into clusters (`true`/`false`).
|
||||
* `quadrants_enabled`: Display quadrants on the chart (`true`/`false`).
|
||||
* `quadrant_properties`: Configuration for quadrant labels and colors.
|
||||
* `custom_quadrant_value_x`, `custom_quadrant_value_y`: Set quadrant boundaries as a percentage.
|
||||
* `custom_quadrant_point_x`, `custom_quadrant_point_y`: Set quadrant boundaries to a specific value.
|
||||
|
||||
**Miscellaneous**
|
||||
* `reference_lines`: Configuration for displaying reference lines.
|
||||
* `trend_lines`: Configuration for displaying trend lines.
|
||||
* `trellis`: Configuration for creating trellis (small multiple) charts.
|
||||
* `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering interactions.
|
||||
|
||||
### Boxplot
|
||||
|
||||
* Inherits most of the Cartesian chart options.
|
||||
* `type`: Must be `looker_boxplot`.
|
||||
|
||||
### Funnel
|
||||
|
||||
* `type`: Must be `looker_funnel`.
|
||||
* `orientation`: How data is read (`'automatic'`, `'dataInRows'`, `'dataInColumns'`).
|
||||
* `percentType`: How percentages are calculated (`'percentOfMaxValue'`, `'percentOfPriorRow'`).
|
||||
* `labelPosition`, `valuePosition`, `percentPosition`: Placement of labels (`'left'`, `'right'`, `'inline'`, `'hidden'`).
|
||||
* `labelColor`, `labelColorEnabled`: Set a custom color for labels.
|
||||
* `labelOverlap`: Allow labels to overlap (`true`/`false`).
|
||||
* `barColors`: An array of colors for the funnel steps.
|
||||
* `color_application`: Advanced color palette controls.
|
||||
* `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
|
||||
|
||||
### Pie / Donut
|
||||
|
||||
* `type`: Must be `looker_pie`.
|
||||
* `value_labels`: Where to display values (`'legend'`, `'labels'`).
|
||||
* `label_type`: The format of data labels (`'labPer'`, `'labVal'`, `'lab'`, `'val'`, `'per'`).
|
||||
* `start_angle`, `end_angle`: The start and end angles of the pie chart.
|
||||
* `inner_radius`: The inner radius, used to create a donut chart.
|
||||
* `series_colors`, `series_labels`: Override colors and labels for specific slices.
|
||||
* `color_application`: Advanced color palette controls.
|
||||
* `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
|
||||
* `advanced_vis_config`: A string containing JSON for advanced Highcharts configuration.
|
||||
|
||||
### Waterfall
|
||||
|
||||
* Inherits most of the Cartesian chart options.
|
||||
* `type`: Must be `looker_waterfall`.
|
||||
* `up_color`: Color for positive (increasing) values.
|
||||
* `down_color`: Color for negative (decreasing) values.
|
||||
* `total_color`: Color for the total bar.
|
||||
|
||||
### Word Cloud
|
||||
|
||||
* `type`: Must be `looker_wordcloud`.
|
||||
* `rotation`: Enable random word rotation (`true`/`false`).
|
||||
* `colors`: An array of colors for the words.
|
||||
* `color_application`: Advanced color palette controls.
|
||||
* `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
|
||||
|
||||
These are some sample vis_config settings.
|
||||
|
||||
A bar chart -
|
||||
{{
|
||||
@@ -307,13 +420,29 @@ tools:
|
||||
"y_axis_zoom": true
|
||||
}}
|
||||
|
||||
A single value visualization -
|
||||
A single record visualization -
|
||||
{{
|
||||
"defaults_version": 1,
|
||||
"show_view_names": false,
|
||||
"type": "looker_single_record"
|
||||
}}
|
||||
|
||||
A single value visualization -
|
||||
{{
|
||||
"comparison_reverse_colors": false,
|
||||
"comparison_type": "value", "conditional_formatting_include_nulls": false, "conditional_formatting_include_totals": false,
|
||||
"custom_color": "#1A73E8",
|
||||
"custom_color_enabled": true,
|
||||
"defaults_version": 1,
|
||||
"enable_conditional_formatting": false,
|
||||
"series_types": {},
|
||||
"show_comparison": false,
|
||||
"show_comparison_label": true,
|
||||
"show_single_value_title": true,
|
||||
"single_value_title": "Total Clicks",
|
||||
"type": "single_value"
|
||||
}}
|
||||
|
||||
A Pie chart -
|
||||
{{
|
||||
"defaults_version": 1,
|
||||
|
||||
@@ -127,8 +127,121 @@ tools:
|
||||
parameter.
|
||||
|
||||
The vis_config is optional. If provided, it will be used to
|
||||
control the default visualization for the query. These are
|
||||
some sample vis_config settings.
|
||||
control the default visualization for the query. Here are
|
||||
some notes on making visualizations.
|
||||
|
||||
### Cartesian Charts (Area, Bar, Column, Line, Scatter)
|
||||
|
||||
These chart types share a large number of configuration options.
|
||||
|
||||
**General**
|
||||
* `type`: The type of visualization (`looker_area`, `looker_bar`, `looker_column`, `looker_line`, `looker_scatter`).
|
||||
* `series_types`: Override the chart type for individual series.
|
||||
* `show_view_names`: Display view names in labels and tooltips (`true`/`false`).
|
||||
* `series_labels`: Provide custom names for series.
|
||||
|
||||
**Styling & Colors**
|
||||
* `colors`: An array of color values to be used for the chart series.
|
||||
* `series_colors`: A mapping of series names to specific color values.
|
||||
* `color_application`: Advanced controls for color palette application (collection, palette, reverse, etc.).
|
||||
* `font_size`: Font size for labels (e.g., '12px').
|
||||
|
||||
**Legend**
|
||||
* `hide_legend`: Show or hide the chart legend (`true`/`false`).
|
||||
* `legend_position`: Placement of the legend (`'center'`, `'left'`, `'right'`).
|
||||
|
||||
**Axes**
|
||||
* `swap_axes`: Swap the X and Y axes (`true`/`false`).
|
||||
* `x_axis_scale`: Scale of the x-axis (`'auto'`, `'ordinal'`, `'linear'`, `'time'`).
|
||||
* `x_axis_reversed`, `y_axis_reversed`: Reverse the direction of an axis (`true`/`false`).
|
||||
* `x_axis_gridlines`, `y_axis_gridlines`: Display gridlines for an axis (`true`/`false`).
|
||||
* `show_x_axis_label`, `show_y_axis_label`: Show or hide the axis title (`true`/`false`).
|
||||
* `show_x_axis_ticks`, `show_y_axis_ticks`: Show or hide axis tick marks (`true`/`false`).
|
||||
* `x_axis_label`, `y_axis_label`: Set a custom title for an axis.
|
||||
* `x_axis_datetime_label`: A format string for datetime labels on the x-axis (e.g., `'%Y-%m'`).
|
||||
* `x_padding_left`, `x_padding_right`: Adjust padding on the ends of the x-axis.
|
||||
* `x_axis_label_rotation`, `x_axis_label_rotation_bar`: Set rotation for x-axis labels.
|
||||
* `x_axis_zoom`, `y_axis_zoom`: Enable zooming on an axis (`true`/`false`).
|
||||
* `y_axes`: An array of configuration objects for multiple y-axes.
|
||||
|
||||
**Data & Series**
|
||||
* `stacking`: How to stack series (`''` for none, `'normal'`, `'percent'`).
|
||||
* `ordering`: Order of series in a stack (`'none'`, etc.).
|
||||
* `limit_displayed_rows`: Enable or disable limiting the number of rows displayed (`true`/`false`).
|
||||
* `limit_displayed_rows_values`: Configuration for the row limit (e.g., `{ "first_last": "first", "show_hide": "show", "num_rows": 10 }`).
|
||||
* `discontinuous_nulls`: How to render null values in line charts (`true`/`false`).
|
||||
* `point_style`: Style for points on line and area charts (`'none'`, `'circle'`, `'circle_outline'`).
|
||||
* `series_point_styles`: Override point styles for individual series.
|
||||
* `interpolation`: Line interpolation style (`'linear'`, `'monotone'`, `'step'`, etc.).
|
||||
* `show_value_labels`: Display values on data points (`true`/`false`).
|
||||
* `label_value_format`: A format string for value labels.
|
||||
* `show_totals_labels`: Display total labels on stacked charts (`true`/`false`).
|
||||
* `totals_color`: Color for total labels.
|
||||
* `show_silhouette`: Display a "silhouette" of hidden series in stacked charts (`true`/`false`).
|
||||
* `hidden_series`: An array of series names to hide from the visualization.
|
||||
|
||||
**Scatter/Bubble Specific**
|
||||
* `size_by_field`: The field used to determine the size of bubbles.
|
||||
* `color_by_field`: The field used to determine the color of bubbles.
|
||||
* `plot_size_by_field`: Whether to display the size-by field in the legend.
|
||||
* `cluster_points`: Group nearby points into clusters (`true`/`false`).
|
||||
* `quadrants_enabled`: Display quadrants on the chart (`true`/`false`).
|
||||
* `quadrant_properties`: Configuration for quadrant labels and colors.
|
||||
* `custom_quadrant_value_x`, `custom_quadrant_value_y`: Set quadrant boundaries as a percentage.
|
||||
* `custom_quadrant_point_x`, `custom_quadrant_point_y`: Set quadrant boundaries to a specific value.
|
||||
|
||||
**Miscellaneous**
|
||||
* `reference_lines`: Configuration for displaying reference lines.
|
||||
* `trend_lines`: Configuration for displaying trend lines.
|
||||
* `trellis`: Configuration for creating trellis (small multiple) charts.
|
||||
* `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering interactions.
|
||||
|
||||
### Boxplot
|
||||
|
||||
* Inherits most of the Cartesian chart options.
|
||||
* `type`: Must be `looker_boxplot`.
|
||||
|
||||
### Funnel
|
||||
|
||||
* `type`: Must be `looker_funnel`.
|
||||
* `orientation`: How data is read (`'automatic'`, `'dataInRows'`, `'dataInColumns'`).
|
||||
* `percentType`: How percentages are calculated (`'percentOfMaxValue'`, `'percentOfPriorRow'`).
|
||||
* `labelPosition`, `valuePosition`, `percentPosition`: Placement of labels (`'left'`, `'right'`, `'inline'`, `'hidden'`).
|
||||
* `labelColor`, `labelColorEnabled`: Set a custom color for labels.
|
||||
* `labelOverlap`: Allow labels to overlap (`true`/`false`).
|
||||
* `barColors`: An array of colors for the funnel steps.
|
||||
* `color_application`: Advanced color palette controls.
|
||||
* `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
|
||||
|
||||
### Pie / Donut
|
||||
|
||||
* `type`: Must be `looker_pie`.
|
||||
* `value_labels`: Where to display values (`'legend'`, `'labels'`).
|
||||
* `label_type`: The format of data labels (`'labPer'`, `'labVal'`, `'lab'`, `'val'`, `'per'`).
|
||||
* `start_angle`, `end_angle`: The start and end angles of the pie chart.
|
||||
* `inner_radius`: The inner radius, used to create a donut chart.
|
||||
* `series_colors`, `series_labels`: Override colors and labels for specific slices.
|
||||
* `color_application`: Advanced color palette controls.
|
||||
* `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
|
||||
* `advanced_vis_config`: A string containing JSON for advanced Highcharts configuration.
|
||||
|
||||
### Waterfall
|
||||
|
||||
* Inherits most of the Cartesian chart options.
|
||||
* `type`: Must be `looker_waterfall`.
|
||||
* `up_color`: Color for positive (increasing) values.
|
||||
* `down_color`: Color for negative (decreasing) values.
|
||||
* `total_color`: Color for the total bar.
|
||||
|
||||
### Word Cloud
|
||||
|
||||
* `type`: Must be `looker_wordcloud`.
|
||||
* `rotation`: Enable random word rotation (`true`/`false`).
|
||||
* `colors`: An array of colors for the words.
|
||||
* `color_application`: Advanced color palette controls.
|
||||
* `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
|
||||
|
||||
These are some sample vis_config settings.
|
||||
|
||||
A bar chart -
|
||||
{{
|
||||
@@ -388,13 +501,29 @@ tools:
|
||||
"y_axis_zoom": true
|
||||
}}
|
||||
|
||||
A single value visualization -
|
||||
A single record visualization -
|
||||
{{
|
||||
"defaults_version": 1,
|
||||
"show_view_names": false,
|
||||
"type": "looker_single_record"
|
||||
}}
|
||||
|
||||
A single value visualization -
|
||||
{{
|
||||
"comparison_reverse_colors": false,
|
||||
"comparison_type": "value", "conditional_formatting_include_nulls": false, "conditional_formatting_include_totals": false,
|
||||
"custom_color": "#1A73E8",
|
||||
"custom_color_enabled": true,
|
||||
"defaults_version": 1,
|
||||
"enable_conditional_formatting": false,
|
||||
"series_types": {},
|
||||
"show_comparison": false,
|
||||
"show_comparison_label": true,
|
||||
"show_single_value_title": true,
|
||||
"single_value_title": "Total Clicks",
|
||||
"type": "single_value"
|
||||
}}
|
||||
|
||||
A Pie chart -
|
||||
{{
|
||||
"defaults_version": 1,
|
||||
@@ -483,6 +612,59 @@ tools:
|
||||
The result is a json document with a link to the newly
|
||||
created look.
|
||||
|
||||
get_dashboards:
|
||||
kind: looker-get-dashboards
|
||||
source: looker-source
|
||||
description: |
|
||||
get_dashboards Tool
|
||||
|
||||
This tool is used to search for saved dashboards in a Looker instance.
|
||||
String search params use case-insensitive matching. String search
|
||||
params can contain % and '_' as SQL LIKE pattern match wildcard
|
||||
expressions. example="dan%" will match "danger" and "Danzig" but
|
||||
not "David" example="D_m%" will match "Damage" and "dump".
|
||||
Most search params can accept "IS NULL" and "NOT NULL" as special
|
||||
expressions to match or exclude (respectively) rows where the
|
||||
column is null.
|
||||
|
||||
The limit and offset are used to paginate the results.
|
||||
|
||||
The result of the get_dashboards tool is a list of json objects.
|
||||
|
||||
make_dashboard:
|
||||
kind: looker-make-dashboard
|
||||
source: looker-source
|
||||
description: |
|
||||
make_dashboard Tool
|
||||
|
||||
This tool creates a new dashboard in Looker. The dashboard is
|
||||
initially empty and the add_dashboard_element tool is used to
|
||||
add content to the dashboard.
|
||||
|
||||
The newly created dashboard will be created in the user's
|
||||
personal folder in looker. The dashboard name must be unique.
|
||||
|
||||
The result is a json document with a link to the newly
|
||||
created dashboard and the id of the dashboard. Use the id
|
||||
when calling add_dashboard_element.
|
||||
|
||||
add_dashboard_element:
|
||||
kind: looker-add-dashboard-element
|
||||
source: looker-source
|
||||
description: |
|
||||
add_dashboard_element Tool
|
||||
|
||||
This tool creates a new tile in a Looker dashboard using
|
||||
the query parameters and the vis_config specified.
|
||||
|
||||
Most of the parameters are the same as the query_url
|
||||
tool. In addition, there is a title that may be provided.
|
||||
The dashboard_id must be specified. That is obtained
|
||||
from calling make_dashboard.
|
||||
|
||||
This tool can be called many times for one dashboard_id
|
||||
and the resulting tiles will be added in order.
|
||||
|
||||
toolsets:
|
||||
looker-tools:
|
||||
- get_models
|
||||
@@ -497,3 +679,6 @@ toolsets:
|
||||
- get_looks
|
||||
- run_look
|
||||
- make_look
|
||||
- get_dashboards
|
||||
- make_dashboard
|
||||
- add_dashboard_element
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed 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.
|
||||
package lookeradddashboardelement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
|
||||
"github.com/looker-open-source/sdk-codegen/go/rtl"
|
||||
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
|
||||
)
|
||||
|
||||
const kind string = "looker-add-dashboard-element"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
|
||||
actual := Config{Name: name}
|
||||
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actual, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(*lookersrc.Source)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
|
||||
}
|
||||
|
||||
parameters := lookercommon.GetQueryParameters()
|
||||
|
||||
dashIdParameter := tools.NewStringParameter("dashboard_id", "The id of the dashboard where this tile will exist")
|
||||
parameters = append(parameters, dashIdParameter)
|
||||
titleParameter := tools.NewStringParameterWithDefault("title", "", "The title of the Dashboard Element")
|
||||
parameters = append(parameters, titleParameter)
|
||||
vizParameter := tools.NewMapParameterWithDefault("vis_config",
|
||||
map[string]any{},
|
||||
"The visualization config for the query",
|
||||
"",
|
||||
)
|
||||
parameters = append(parameters, vizParameter)
|
||||
|
||||
mcpManifest := tools.McpManifest{
|
||||
Name: cfg.Name,
|
||||
Description: cfg.Description,
|
||||
InputSchema: parameters.McpManifest(),
|
||||
}
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Client: s.Client,
|
||||
ApiSettings: s.ApiSettings,
|
||||
manifest: tools.Manifest{
|
||||
Description: cfg.Description,
|
||||
Parameters: parameters.Manifest(),
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
},
|
||||
mcpManifest: mcpManifest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
Client *v4.LookerSDK
|
||||
ApiSettings *rtl.ApiSettings
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
var (
|
||||
dataType string = "data"
|
||||
visType string = "vis"
|
||||
)
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
|
||||
logger, err := util.LoggerFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
|
||||
}
|
||||
logger.DebugContext(ctx, "params = ", params)
|
||||
wq, err := lookercommon.ProcessQueryArgs(ctx, params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building query request: %w", err)
|
||||
}
|
||||
|
||||
paramsMap := params.AsMap()
|
||||
dashboard_id := paramsMap["dashboard_id"].(string)
|
||||
title := paramsMap["title"].(string)
|
||||
|
||||
visConfig := paramsMap["vis_config"].(map[string]any)
|
||||
wq.VisConfig = &visConfig
|
||||
|
||||
qrespFields := "id"
|
||||
qresp, err := t.Client.CreateQuery(*wq, qrespFields, t.ApiSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making create query request: %s", err)
|
||||
}
|
||||
|
||||
wde := v4.WriteDashboardElement{
|
||||
DashboardId: &dashboard_id,
|
||||
Title: &title,
|
||||
QueryId: qresp.Id,
|
||||
}
|
||||
switch len(visConfig) {
|
||||
case 0:
|
||||
wde.Type = &dataType
|
||||
default:
|
||||
wde.Type = &visType
|
||||
}
|
||||
|
||||
fields := ""
|
||||
|
||||
req := v4.RequestCreateDashboardElement{
|
||||
Body: wde,
|
||||
Fields: &fields,
|
||||
}
|
||||
|
||||
resp, err := t.Client.CreateDashboardElement(req, t.ApiSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making create dashboard element request: %s", err)
|
||||
}
|
||||
logger.DebugContext(ctx, "resp = %v", resp)
|
||||
|
||||
data := make(map[string]any)
|
||||
|
||||
data["result"] = fmt.Sprintf("Dashboard element added to dashboard %s", dashboard_id)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed 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.
|
||||
|
||||
package lookeradddashboardelement_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardelement"
|
||||
)
|
||||
|
||||
func TestParseFromYamlLookerAddDashboardElement(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: looker-add-dashboard-element
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": lkr.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "looker-add-dashboard-element",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFailParseFromYamlLookerAddDashboardElement(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
desc: "Invalid method",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: looker-add-dashboard-element
|
||||
source: my-instance
|
||||
method: GOT
|
||||
description: some description
|
||||
`,
|
||||
err: "unable to parse tool \"example_tool\" as kind \"looker-add-dashboard-element\": [4:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n 3 | kind: looker-add-dashboard-element\n> 4 | method: GOT\n ^\n 5 | source: my-instance",
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err == nil {
|
||||
t.Fatalf("expect parsing to fail")
|
||||
}
|
||||
errStr := err.Error()
|
||||
if !strings.Contains(errStr, tc.err) {
|
||||
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
188
internal/tools/looker/lookergetdashboards/lookergetdashboards.go
Normal file
188
internal/tools/looker/lookergetdashboards/lookergetdashboards.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed 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.
|
||||
package lookergetdashboards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
|
||||
"github.com/looker-open-source/sdk-codegen/go/rtl"
|
||||
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
|
||||
)
|
||||
|
||||
const kind string = "looker-get-dashboards"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
|
||||
actual := Config{Name: name}
|
||||
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actual, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(*lookersrc.Source)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
|
||||
}
|
||||
|
||||
titleParameter := tools.NewStringParameterWithDefault("title", "", "The title of the dashboard.")
|
||||
descParameter := tools.NewStringParameterWithDefault("desc", "", "The description of the dashboard.")
|
||||
limitParameter := tools.NewIntParameterWithDefault("limit", 100, "The number of dashboards to fetch. Default 100")
|
||||
offsetParameter := tools.NewIntParameterWithDefault("offset", 0, "The number of dashboards to skip before fetching. Default 0")
|
||||
parameters := tools.Parameters{
|
||||
titleParameter,
|
||||
descParameter,
|
||||
limitParameter,
|
||||
offsetParameter,
|
||||
}
|
||||
|
||||
mcpManifest := tools.McpManifest{
|
||||
Name: cfg.Name,
|
||||
Description: cfg.Description,
|
||||
InputSchema: parameters.McpManifest(),
|
||||
}
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Client: s.Client,
|
||||
ApiSettings: s.ApiSettings,
|
||||
manifest: tools.Manifest{
|
||||
Description: cfg.Description,
|
||||
Parameters: parameters.Manifest(),
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
},
|
||||
mcpManifest: mcpManifest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
Client *v4.LookerSDK
|
||||
ApiSettings *rtl.ApiSettings
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
|
||||
logger, err := util.LoggerFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
|
||||
}
|
||||
paramsMap := params.AsMap()
|
||||
title := paramsMap["title"].(string)
|
||||
title_ptr := &title
|
||||
if *title_ptr == "" {
|
||||
title_ptr = nil
|
||||
}
|
||||
desc := paramsMap["desc"].(string)
|
||||
desc_ptr := &desc
|
||||
if *desc_ptr == "" {
|
||||
desc_ptr = nil
|
||||
}
|
||||
limit := int64(paramsMap["limit"].(int))
|
||||
offset := int64(paramsMap["offset"].(int))
|
||||
|
||||
req := v4.RequestSearchDashboards{
|
||||
Title: title_ptr,
|
||||
Description: desc_ptr,
|
||||
Limit: &limit,
|
||||
Offset: &offset,
|
||||
}
|
||||
logger.ErrorContext(ctx, "Making request %v", req)
|
||||
resp, err := t.Client.SearchDashboards(req, t.ApiSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making get_dashboards request: %s", err)
|
||||
}
|
||||
logger.ErrorContext(ctx, "Got response %v", resp)
|
||||
var data []any
|
||||
for _, v := range resp {
|
||||
logger.DebugContext(ctx, "Got response element of %v\n", v)
|
||||
vMap := make(map[string]any)
|
||||
if v.Id != nil {
|
||||
vMap["id"] = *v.Id
|
||||
}
|
||||
if v.Title != nil {
|
||||
vMap["title"] = *v.Title
|
||||
}
|
||||
if v.Description != nil {
|
||||
vMap["description"] = *v.Description
|
||||
}
|
||||
logger.DebugContext(ctx, "Converted to %v\n", vMap)
|
||||
data = append(data, vMap)
|
||||
}
|
||||
logger.DebugContext(ctx, "data = ", data)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed 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.
|
||||
|
||||
package lookergetdashboards_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetdashboards"
|
||||
)
|
||||
|
||||
func TestParseFromYamlLookerGetDashboards(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: looker-get-dashboards
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": lkr.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "looker-get-dashboards",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFailParseFromYamlLookerGetDashboards(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
desc: "Invalid method",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: looker-get-dashboards
|
||||
source: my-instance
|
||||
method: GOT
|
||||
description: some description
|
||||
`,
|
||||
err: "unable to parse tool \"example_tool\" as kind \"looker-get-dashboards\": [4:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n 3 | kind: looker-get-dashboards\n> 4 | method: GOT\n ^\n 5 | source: my-instance",
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err == nil {
|
||||
t.Fatalf("expect parsing to fail")
|
||||
}
|
||||
errStr := err.Error()
|
||||
if !strings.Contains(errStr, tc.err) {
|
||||
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
201
internal/tools/looker/lookermakedashboard/lookermakedashboard.go
Normal file
201
internal/tools/looker/lookermakedashboard/lookermakedashboard.go
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed 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.
|
||||
package lookermakedashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
|
||||
"github.com/looker-open-source/sdk-codegen/go/rtl"
|
||||
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
|
||||
)
|
||||
|
||||
const kind string = "looker-make-dashboard"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
|
||||
actual := Config{Name: name}
|
||||
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actual, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(*lookersrc.Source)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{}
|
||||
|
||||
titleParameter := tools.NewStringParameter("title", "The title of the Dashboard")
|
||||
parameters = append(parameters, titleParameter)
|
||||
descParameter := tools.NewStringParameterWithDefault("description", "", "The description of the Dashboard")
|
||||
parameters = append(parameters, descParameter)
|
||||
|
||||
mcpManifest := tools.McpManifest{
|
||||
Name: cfg.Name,
|
||||
Description: cfg.Description,
|
||||
InputSchema: parameters.McpManifest(),
|
||||
}
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Client: s.Client,
|
||||
ApiSettings: s.ApiSettings,
|
||||
manifest: tools.Manifest{
|
||||
Description: cfg.Description,
|
||||
Parameters: parameters.Manifest(),
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
},
|
||||
mcpManifest: mcpManifest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
Client *v4.LookerSDK
|
||||
ApiSettings *rtl.ApiSettings
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
|
||||
logger, err := util.LoggerFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
|
||||
}
|
||||
logger.DebugContext(ctx, "params = ", params)
|
||||
|
||||
mrespFields := "id,personal_folder_id"
|
||||
mresp, err := t.Client.Me(mrespFields, t.ApiSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making me request: %s", err)
|
||||
}
|
||||
|
||||
paramsMap := params.AsMap()
|
||||
title := paramsMap["title"].(string)
|
||||
description := paramsMap["description"].(string)
|
||||
|
||||
if mresp.PersonalFolderId == nil || *mresp.PersonalFolderId == "" {
|
||||
return nil, fmt.Errorf("user does not have a personal folder. cannot continue")
|
||||
}
|
||||
|
||||
dashs, err := t.Client.FolderDashboards(*mresp.PersonalFolderId, "title", t.ApiSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting existing dashboards in folder: %s", err)
|
||||
}
|
||||
|
||||
dashTitles := []string{}
|
||||
for _, dash := range dashs {
|
||||
dashTitles = append(dashTitles, *dash.Title)
|
||||
}
|
||||
if slices.Contains(dashTitles, title) {
|
||||
lt, _ := json.Marshal(dashTitles)
|
||||
return nil, fmt.Errorf("title %s already used in user's folder. Currently used titles are %v. Make the call again with a unique title", title, string(lt))
|
||||
}
|
||||
|
||||
wd := v4.WriteDashboard{
|
||||
Title: &title,
|
||||
Description: &description,
|
||||
FolderId: mresp.PersonalFolderId,
|
||||
}
|
||||
resp, err := t.Client.CreateDashboard(wd, t.ApiSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making create dashboard request: %s", err)
|
||||
}
|
||||
logger.DebugContext(ctx, "resp = %v", resp)
|
||||
|
||||
setting, err := t.Client.GetSetting("host_url", t.ApiSettings)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "error getting settings: %s", err)
|
||||
}
|
||||
|
||||
data := make(map[string]any)
|
||||
if resp.Id != nil {
|
||||
data["id"] = *resp.Id
|
||||
}
|
||||
if resp.Url != nil {
|
||||
if setting.HostUrl != nil {
|
||||
data["url"] = *setting.HostUrl + *resp.Url
|
||||
} else {
|
||||
data["url"] = *resp.Url
|
||||
}
|
||||
}
|
||||
logger.DebugContext(ctx, "data = %v", data)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed 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.
|
||||
|
||||
package lookermakedashboard_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookermakedashboard"
|
||||
)
|
||||
|
||||
func TestParseFromYamlLookerMakeDashboard(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: looker-make-dashboard
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": lkr.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "looker-make-dashboard",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFailParseFromYamlMakeDashboard(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
desc: "Invalid method",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: looker-make-dashboard
|
||||
source: my-instance
|
||||
method: GOT
|
||||
description: some description
|
||||
`,
|
||||
err: "unable to parse tool \"example_tool\" as kind \"looker-make-dashboard\": [4:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n 3 | kind: looker-make-dashboard\n> 4 | method: GOT\n ^\n 5 | source: my-instance",
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err == nil {
|
||||
t.Fatalf("expect parsing to fail")
|
||||
}
|
||||
errStr := err.Error()
|
||||
if !strings.Contains(errStr, tc.err) {
|
||||
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookermakelook"
|
||||
)
|
||||
|
||||
func TestParseFromYamlLookerQuery(t *testing.T) {
|
||||
func TestParseFromYamlLookerMakeLook(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
@@ -73,7 +73,7 @@ func TestParseFromYamlLookerQuery(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestFailParseFromYamlLookerQuery(t *testing.T) {
|
||||
func TestFailParseFromYamlLookerMakeLook(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
|
||||
@@ -120,6 +120,16 @@ func TestLooker(t *testing.T) {
|
||||
"source": "my-instance",
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
},
|
||||
"get_looks": map[string]any{
|
||||
"kind": "looker-get-looks",
|
||||
"source": "my-instance",
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
},
|
||||
"get_dashboards": map[string]any{
|
||||
"kind": "looker-get-dashboards",
|
||||
"source": "my-instance",
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -347,6 +357,266 @@ func TestLooker(t *testing.T) {
|
||||
},
|
||||
},
|
||||
)
|
||||
tests.RunToolGetTestByName(t, "query_sql",
|
||||
map[string]any{
|
||||
"query_sql": map[string]any{
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
"authRequired": []any{},
|
||||
"parameters": []any{
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The model containing the explore.",
|
||||
"name": "model",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The explore to be queried.",
|
||||
"name": "explore",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The fields to be retrieved.",
|
||||
"items": map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "A field to be returned in the query",
|
||||
"name": "field",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
"name": "fields",
|
||||
"required": true,
|
||||
"type": "array",
|
||||
},
|
||||
map[string]any{
|
||||
"AdditionalProperties": true,
|
||||
"authSources": []any{},
|
||||
"description": "The filters for the query",
|
||||
"name": "filters",
|
||||
"required": false,
|
||||
"type": "object",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The query pivots (must be included in fields as well).",
|
||||
"items": map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "A field to be used as a pivot in the query",
|
||||
"name": "pivot_field",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
"name": "pivots",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The sorts like \"field.id desc 0\".",
|
||||
"items": map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "A field to be used as a sort in the query",
|
||||
"name": "sort_field",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
"name": "sorts",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The row limit.",
|
||||
"name": "limit",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The query timezone.",
|
||||
"name": "tz",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
tests.RunToolGetTestByName(t, "query_url",
|
||||
map[string]any{
|
||||
"query_url": map[string]any{
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
"authRequired": []any{},
|
||||
"parameters": []any{
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The model containing the explore.",
|
||||
"name": "model",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The explore to be queried.",
|
||||
"name": "explore",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The fields to be retrieved.",
|
||||
"items": map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "A field to be returned in the query",
|
||||
"name": "field",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
"name": "fields",
|
||||
"required": true,
|
||||
"type": "array",
|
||||
},
|
||||
map[string]any{
|
||||
"AdditionalProperties": true,
|
||||
"authSources": []any{},
|
||||
"description": "The filters for the query",
|
||||
"name": "filters",
|
||||
"required": false,
|
||||
"type": "object",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The query pivots (must be included in fields as well).",
|
||||
"items": map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "A field to be used as a pivot in the query",
|
||||
"name": "pivot_field",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
"name": "pivots",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The sorts like \"field.id desc 0\".",
|
||||
"items": map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "A field to be used as a sort in the query",
|
||||
"name": "sort_field",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
"name": "sorts",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The row limit.",
|
||||
"name": "limit",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The query timezone.",
|
||||
"name": "tz",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"AdditionalProperties": true,
|
||||
"authSources": []any{},
|
||||
"description": "The visualization config for the query",
|
||||
"name": "vis_config",
|
||||
"required": false,
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
tests.RunToolGetTestByName(t, "get_looks",
|
||||
map[string]any{
|
||||
"get_looks": map[string]any{
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
"authRequired": []any{},
|
||||
"parameters": []any{
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The title of the look.",
|
||||
"name": "title",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The description of the look.",
|
||||
"name": "desc",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The number of looks to fetch. Default 100",
|
||||
"name": "limit",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The number of looks to skip before fetching. Default 0",
|
||||
"name": "offset",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
tests.RunToolGetTestByName(t, "get_dashboards",
|
||||
map[string]any{
|
||||
"get_dashboards": map[string]any{
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
"authRequired": []any{},
|
||||
"parameters": []any{
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The title of the dashboard.",
|
||||
"name": "title",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The description of the dashboard.",
|
||||
"name": "desc",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The number of dashboards to fetch. Default 100",
|
||||
"name": "limit",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The number of dashboards to skip before fetching. Default 0",
|
||||
"name": "offset",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
wantResult := "{\"label\":\"System Activity\",\"name\":\"system__activity\",\"project_name\":\"system__activity\"}"
|
||||
tests.RunToolInvokeSimpleTest(t, "get_models", wantResult)
|
||||
@@ -374,4 +644,11 @@ func TestLooker(t *testing.T) {
|
||||
|
||||
wantResult = "system__activity"
|
||||
tests.RunToolInvokeParametersTest(t, "query_url", []byte(`{"model": "system__activity", "explore": "look", "fields": ["look.count"]}`), wantResult)
|
||||
|
||||
// A system that is just being used for testing has no looks or dashboards
|
||||
wantResult = "null"
|
||||
tests.RunToolInvokeParametersTest(t, "get_looks", []byte(`{"title": "FOO", "desc": "BAR"}`), wantResult)
|
||||
|
||||
wantResult = "null"
|
||||
tests.RunToolInvokeParametersTest(t, "get_dashboards", []byte(`{"title": "FOO", "desc": "BAR"}`), wantResult)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user