Skip to content

Commit

Permalink
get dashboard/<id>/tabs endpoint
Browse files Browse the repository at this point in the history
recursive api
  • Loading branch information
fisjac committed May 2, 2024
1 parent e943604 commit 4b34bd6
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 1 deletion.
77 changes: 77 additions & 0 deletions superset-frontend/src/features/alerts/components/TabSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React from 'react';
import { t } from '@superset-ui/core';
import { StyledInputContainer } from '../AlertReportModal';
import { Select, TreeSelect } from 'src/components';

Check failure on line 23 in superset-frontend/src/features/alerts/components/TabSelect.tsx

View workflow job for this annotation

GitHub Actions / frontend-build

`src/components` import should occur before import of `../AlertReportModal`

// we need to build a tab select with just single dropdown options
// then we need a tree select component that will build out until
// the tree reaches a leaf that has subgroups and needs to be a dropdown

// const traverseTabs = (tabs) => {
// if (tabs.length > 1) {
// console.log("make into ")
// }
// }

type SelectValue = {
value: string;
label: string;
};

export const TabSelect = tabs => {
console.log(tabs);

// const tabParse = (children) => {
// if (tabs.length > 1) {
// children.forEach(childGroup=>selectionsList.append(childGroup))
// } else {

// }
// }

const selectionsList = [];
// tabParse(tabs)
console.log(selectionsList);
const TreeContainer = nodes => {

Check failure on line 54 in superset-frontend/src/features/alerts/components/TabSelect.tsx

View workflow job for this annotation

GitHub Actions / frontend-build

Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`
return (
<StyledInputContainer>
<>
<div className="control-label">{t('Select tab')}</div>
<TreeSelect
// disabled={tabOptions?.length === 0}
// treeData={tabs}
// value={selectedTab?.value}
// onSelect={onTabSelect}
style={{ width: '100%' }}
placeholder={t('Select a tab')}
/>
</>
</StyledInputContainer>
);
};

return (
<Select
options={tabs.map(tab => ({ value: tab.id, label: tab.meta.text }))}
/>
);
};
1 change: 1 addition & 0 deletions superset/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class RouteMethod: # pylint: disable=too-few-public-methods
"data_from_cache": "read",
"get_charts": "read",
"get_datasets": "read",
"get_tabs": "read",
"function_names": "read",
"available": "read",
"validate_sql": "read",
Expand Down
5 changes: 5 additions & 0 deletions superset/daos/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ def get_datasets_for_dashboard(id_or_slug: str) -> list[Any]:
dashboard = DashboardDAO.get_by_id_or_slug(id_or_slug)
return dashboard.datasets_trimmed_for_slices()

@staticmethod
def get_tabs_for_dashboard(id_or_slug: str) -> dict[str, Any]:
dashboard = DashboardDAO.get_by_id_or_slug(id_or_slug)
return dashboard.tabs

@staticmethod
def get_charts_for_dashboard(id_or_slug: str) -> list[Slice]:
return DashboardDAO.get_by_id_or_slug(id_or_slug).slices
Expand Down
62 changes: 62 additions & 0 deletions superset/dashboards/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
get_fav_star_ids_schema,
GetFavStarIdsSchema,
openapi_spec_methods_override,
TabsPayloadSchema,
thumbnail_query_schema,
)
from superset.extensions import event_logger
Expand Down Expand Up @@ -141,6 +142,7 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]:
"remove_favorite",
"get_charts",
"get_datasets",
"get_tabs",
"get_embedded",
"set_embedded",
"delete_embedded",
Expand Down Expand Up @@ -236,6 +238,7 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]:
chart_entity_response_schema = ChartEntityResponseSchema()
dashboard_get_response_schema = DashboardGetResponseSchema()
dashboard_dataset_schema = DashboardDatasetSchema()
tab_schema = TabsPayloadSchema()
embedded_response_schema = EmbeddedDashboardResponseSchema()
embedded_config_schema = EmbeddedDashboardConfigSchema()

Expand Down Expand Up @@ -268,6 +271,7 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]:
DashboardCopySchema,
DashboardGetResponseSchema,
DashboardDatasetSchema,
TabsPayloadSchema,
GetFavStarIdsSchema,
EmbeddedDashboardResponseSchema,
)
Expand Down Expand Up @@ -395,6 +399,64 @@ def get_datasets(self, id_or_slug: str) -> Response:
except DashboardNotFoundError:
return self.response_404()

@expose("/<id_or_slug>/tabs", methods=("GET",))
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(
action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.get_tabs",
log_to_statsd=False,
)
def get_tabs(self, id_or_slug: str) -> Response:
"""Get dashboard's tabs.
---
get:
summary: Get dashboard's tabs
description: >-
Returns a list of a dashboard's tabs.
parameters:
- in: path
schema:
type: string
name: id_or_slug
description: Either the id of the dashboard, or its slug
responses:
200:
description: Dashboard tabs
content:
application/json:
schema:
type: object
properties:
result:
type: object
items:
$ref: '#/components/schemas/TabSchema'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
403:
$ref: '#/components/responses/403'
404:
$ref: '#/components/responses/404'
"""
try:
tabs = DashboardDAO.get_tabs_for_dashboard(id_or_slug)
result = self.tab_schema.dump(tabs)
return self.response(200, result=result)

except (TypeError, ValueError) as err:
return self.response_400(
message=gettext(
"Tab schema is invalid, caused by: %(error)s", error=str(err)
)
)
except DashboardAccessDeniedError:
return self.response_403()
except DashboardNotFoundError:
return self.response_404()

@expose("/<id_or_slug>/charts", methods=("GET",))
@protect()
@safe
Expand Down
11 changes: 11 additions & 0 deletions superset/dashboards/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,17 @@ def post_dump(self, serialized: dict[str, Any], **kwargs: Any) -> dict[str, Any]
return serialized


class TabSchema(Schema):
children = fields.List(fields.Nested(lambda: TabSchema()))
value = fields.Str()
title = fields.Str()


class TabsPayloadSchema(Schema):
all_tabs = fields.Dict(keys=fields.String(), values=fields.String())
tab_tree = fields.List(fields.Nested(lambda: TabSchema))


class BaseDashboardSchema(Schema):
# pylint: disable=unused-argument
@post_load
Expand Down
51 changes: 50 additions & 1 deletion superset/models/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import json
import logging
import uuid
from collections import defaultdict
from collections import defaultdict, deque
from typing import Any, Callable

import sqlalchemy as sqla
Expand Down Expand Up @@ -298,6 +298,55 @@ def position(self) -> dict[str, Any]:
return json.loads(self.position_json)
return {}

@property
def tabs(self) -> dict[str, Any]:
if self.position == {}:
return {}

def get_node(node_id: str) -> dict[str, Any]:
"""
Helper function for getting a node from the position_data
"""
return self.position[node_id]

def build_tab_tree(
node: dict[str, Any], children: list[dict[str, Any]]
) -> None:
"""
Function for building the tab tree structure and list of all tabs
"""

new_children: list[dict[str, Any]] = []
# new children to overwrite parent's children
for child_id in node["children"]:
child = get_node(child_id)
if node["type"] == "TABS":
# if TABS add create a new list and append children to it
# new_children.append(child)
children.append(child)
queue.append((child, new_children))
elif node["type"] in ["GRID", "ROOT"]:
queue.append((child, children))
elif node["type"] == "TAB":
queue.append((child, new_children))
if node["type"] == "TAB":
node["children"] = new_children
node["title"] = node["meta"]["text"]
node["value"] = node["id"]
all_tabs[node["id"]] = node["title"]
return

root = get_node("ROOT_ID")
tab_tree: list[dict[str, Any]] = []
all_tabs: dict[str, str] = {}
queue: deque[tuple[dict[str, Any], list[dict[str, Any]]]] = deque()
queue.append((root, tab_tree))
while queue:
node, children = queue.popleft()
build_tab_tree(node, children)

return {"all_tabs": all_tabs, "tab_tree": tab_tree}

def update_thumbnail(self) -> None:
cache_dashboard_thumbnail.delay(
current_user=get_current_user(),
Expand Down

0 comments on commit 4b34bd6

Please sign in to comment.