mirror of
https://github.com/getredash/redash.git
synced 2025-12-19 17:37:19 -05:00
Persist updated values and apply saved dashboard parameters (#7570)
Add support for saving dashboard parameters after clicking the Apply button. Parameters are applied in the following order: URL, dashboard parameters, query parameters. Persist the queued values only when “Done Editing” is clicked, keeping Query and Dashboard editors aligned.
This commit is contained in:
committed by
GitHub
parent
761eb0b68b
commit
4353a82c7a
@@ -31,7 +31,8 @@ function DashboardSettings({ dashboardConfiguration }) {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
checked={!!dashboard.dashboard_filters_enabled}
|
checked={!!dashboard.dashboard_filters_enabled}
|
||||||
onChange={({ target }) => updateDashboard({ dashboard_filters_enabled: target.checked })}
|
onChange={({ target }) => updateDashboard({ dashboard_filters_enabled: target.checked })}
|
||||||
data-test="DashboardFiltersCheckbox">
|
data-test="DashboardFiltersCheckbox"
|
||||||
|
>
|
||||||
Use Dashboard Level Filters
|
Use Dashboard Level Filters
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,9 +91,9 @@ function DashboardComponent(props) {
|
|||||||
|
|
||||||
const [pageContainer, setPageContainer] = useState(null);
|
const [pageContainer, setPageContainer] = useState(null);
|
||||||
const [bottomPanelStyles, setBottomPanelStyles] = useState({});
|
const [bottomPanelStyles, setBottomPanelStyles] = useState({});
|
||||||
const onParametersEdit = parameters => {
|
const onParametersEdit = (parameters) => {
|
||||||
const paramOrder = map(parameters, "name");
|
const paramOrder = map(parameters, "name");
|
||||||
updateDashboard({ options: { globalParamOrder: paramOrder } });
|
updateDashboard({ options: { ...dashboard.options, globalParamOrder: paramOrder } });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -175,7 +176,7 @@ function DashboardPage({ dashboardSlug, dashboardId, onError }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Dashboard.get({ id: dashboardId, slug: dashboardSlug })
|
Dashboard.get({ id: dashboardId, slug: dashboardSlug })
|
||||||
.then(dashboardData => {
|
.then((dashboardData) => {
|
||||||
recordEvent("view", "dashboard", dashboardData.id);
|
recordEvent("view", "dashboard", dashboardData.id);
|
||||||
setDashboard(dashboardData);
|
setDashboard(dashboardData);
|
||||||
|
|
||||||
@@ -207,7 +208,7 @@ routes.register(
|
|||||||
"Dashboards.LegacyViewOrEdit",
|
"Dashboards.LegacyViewOrEdit",
|
||||||
routeWithUserSession({
|
routeWithUserSession({
|
||||||
path: "/dashboard/:dashboardSlug",
|
path: "/dashboard/:dashboardSlug",
|
||||||
render: pageProps => <DashboardPage {...pageProps} />,
|
render: (pageProps) => <DashboardPage {...pageProps} />,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -215,6 +216,6 @@ routes.register(
|
|||||||
"Dashboards.ViewOrEdit",
|
"Dashboards.ViewOrEdit",
|
||||||
routeWithUserSession({
|
routeWithUserSession({
|
||||||
path: "/dashboards/:dashboardId([^-]+)(-.*)?",
|
path: "/dashboards/:dashboardId([^-]+)(-.*)?",
|
||||||
render: pageProps => <DashboardPage {...pageProps} />,
|
render: (pageProps) => <DashboardPage {...pageProps} />,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { DashboardStatusEnum } from "../hooks/useDashboard";
|
|||||||
import "./DashboardHeader.less";
|
import "./DashboardHeader.less";
|
||||||
|
|
||||||
function getDashboardTags() {
|
function getDashboardTags() {
|
||||||
return getTags("api/dashboards/tags").then(tags => map(tags, t => t.name));
|
return getTags("api/dashboards/tags").then((tags) => map(tags, (t) => t.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
function buttonType(value) {
|
function buttonType(value) {
|
||||||
@@ -38,7 +38,7 @@ function DashboardPageTitle({ dashboardConfiguration }) {
|
|||||||
<h3>
|
<h3>
|
||||||
<EditInPlace
|
<EditInPlace
|
||||||
isEditable={editingLayout}
|
isEditable={editingLayout}
|
||||||
onDone={name => updateDashboard({ name })}
|
onDone={(name) => updateDashboard({ name })}
|
||||||
value={dashboard.name}
|
value={dashboard.name}
|
||||||
ignoreBlanks
|
ignoreBlanks
|
||||||
/>
|
/>
|
||||||
@@ -53,7 +53,7 @@ function DashboardPageTitle({ dashboardConfiguration }) {
|
|||||||
isArchived={dashboard.is_archived}
|
isArchived={dashboard.is_archived}
|
||||||
canEdit={canEditDashboard}
|
canEdit={canEditDashboard}
|
||||||
getAvailableTags={getDashboardTags}
|
getAvailableTags={getDashboardTags}
|
||||||
onEdit={tags => updateDashboard({ tags })}
|
onEdit={(tags) => updateDashboard({ tags })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -89,14 +89,15 @@ function RefreshButton({ dashboardConfiguration }) {
|
|||||||
placement="bottomRight"
|
placement="bottomRight"
|
||||||
overlay={
|
overlay={
|
||||||
<Menu onClick={onRefreshRateSelected} selectedKeys={[`${refreshRate}`]}>
|
<Menu onClick={onRefreshRateSelected} selectedKeys={[`${refreshRate}`]}>
|
||||||
{refreshRateOptions.map(option => (
|
{refreshRateOptions.map((option) => (
|
||||||
<Menu.Item key={`${option}`} disabled={!includes(allowedIntervals, option)}>
|
<Menu.Item key={`${option}`} disabled={!includes(allowedIntervals, option)}>
|
||||||
{durationHumanize(option)}
|
{durationHumanize(option)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
{refreshRate && <Menu.Item key={null}>Disable auto refresh</Menu.Item>}
|
{refreshRate && <Menu.Item key={null}>Disable auto refresh</Menu.Item>}
|
||||||
</Menu>
|
</Menu>
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
<Button className="icon-button hidden-xs" type={buttonType(refreshRate)}>
|
<Button className="icon-button hidden-xs" type={buttonType(refreshRate)}>
|
||||||
<i className="fa fa-angle-down" aria-hidden="true" />
|
<i className="fa fa-angle-down" aria-hidden="true" />
|
||||||
<span className="sr-only">Split button!</span>
|
<span className="sr-only">Split button!</span>
|
||||||
@@ -166,7 +167,8 @@ function DashboardMoreOptionsButton({ dashboardConfiguration }) {
|
|||||||
<PlainButton onClick={archive}>Archive</PlainButton>
|
<PlainButton onClick={archive}>Archive</PlainButton>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
<Button className="icon-button m-l-5" data-test="DashboardMoreButton" aria-label="More actions">
|
<Button className="icon-button m-l-5" data-test="DashboardMoreButton" aria-label="More actions">
|
||||||
<EllipsisOutlinedIcon rotate={90} aria-hidden="true" />
|
<EllipsisOutlinedIcon rotate={90} aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -216,7 +218,8 @@ function DashboardControl({ dashboardConfiguration, headerExtra }) {
|
|||||||
type={buttonType(fullscreen)}
|
type={buttonType(fullscreen)}
|
||||||
className="icon-button m-l-5"
|
className="icon-button m-l-5"
|
||||||
onClick={toggleFullscreen}
|
onClick={toggleFullscreen}
|
||||||
aria-label="Toggle fullscreen display">
|
aria-label="Toggle fullscreen display"
|
||||||
|
>
|
||||||
<i className="zmdi zmdi-fullscreen" aria-hidden="true" />
|
<i className="zmdi zmdi-fullscreen" aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -229,7 +232,8 @@ function DashboardControl({ dashboardConfiguration, headerExtra }) {
|
|||||||
type={buttonType(dashboard.publicAccessEnabled)}
|
type={buttonType(dashboard.publicAccessEnabled)}
|
||||||
onClick={showShareDashboardDialog}
|
onClick={showShareDashboardDialog}
|
||||||
data-test="OpenShareForm"
|
data-test="OpenShareForm"
|
||||||
aria-label="Share">
|
aria-label="Share"
|
||||||
|
>
|
||||||
<i className="zmdi zmdi-share" aria-hidden="true" />
|
<i className="zmdi zmdi-share" aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -252,7 +256,11 @@ function DashboardEditControl({ dashboardConfiguration, headerExtra }) {
|
|||||||
doneBtnClickedWhileSaving,
|
doneBtnClickedWhileSaving,
|
||||||
dashboardStatus,
|
dashboardStatus,
|
||||||
retrySaveDashboardLayout,
|
retrySaveDashboardLayout,
|
||||||
|
saveDashboardParameters,
|
||||||
} = dashboardConfiguration;
|
} = dashboardConfiguration;
|
||||||
|
const handleDoneEditing = () => {
|
||||||
|
saveDashboardParameters().then(() => setEditingLayout(false));
|
||||||
|
};
|
||||||
let status;
|
let status;
|
||||||
if (dashboardStatus === DashboardStatusEnum.SAVED) {
|
if (dashboardStatus === DashboardStatusEnum.SAVED) {
|
||||||
status = <span className="save-status">Saved</span>;
|
status = <span className="save-status">Saved</span>;
|
||||||
@@ -277,7 +285,7 @@ function DashboardEditControl({ dashboardConfiguration, headerExtra }) {
|
|||||||
Retry
|
Retry
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button loading={doneBtnClickedWhileSaving} type="primary" onClick={() => setEditingLayout(false)}>
|
<Button loading={doneBtnClickedWhileSaving} type="primary" onClick={handleDoneEditing}>
|
||||||
{!doneBtnClickedWhileSaving && <i className="fa fa-check m-r-5" aria-hidden="true" />} Done Editing
|
{!doneBtnClickedWhileSaving && <i className="fa fa-check m-r-5" aria-hidden="true" />} Done Editing
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ export { DashboardStatusEnum } from "./useEditModeHandler";
|
|||||||
|
|
||||||
function getAffectedWidgets(widgets, updatedParameters = []) {
|
function getAffectedWidgets(widgets, updatedParameters = []) {
|
||||||
return !isEmpty(updatedParameters)
|
return !isEmpty(updatedParameters)
|
||||||
? widgets.filter(widget =>
|
? widgets.filter((widget) =>
|
||||||
Object.values(widget.getParameterMappings())
|
Object.values(widget.getParameterMappings())
|
||||||
.filter(({ type }) => type === "dashboard-level")
|
.filter(({ type }) => type === "dashboard-level")
|
||||||
.some(({ mapTo }) =>
|
.some(({ mapTo }) =>
|
||||||
includes(
|
includes(
|
||||||
updatedParameters.map(p => p.name),
|
updatedParameters.map((p) => p.name),
|
||||||
mapTo
|
mapTo
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -50,7 +50,7 @@ function useDashboard(dashboardData) {
|
|||||||
[dashboard]
|
[dashboard]
|
||||||
);
|
);
|
||||||
const hasOnlySafeQueries = useMemo(
|
const hasOnlySafeQueries = useMemo(
|
||||||
() => every(dashboard.widgets, w => (w.getQuery() ? w.getQuery().is_safe : true)),
|
() => every(dashboard.widgets, (w) => (w.getQuery() ? w.getQuery().is_safe : true)),
|
||||||
[dashboard]
|
[dashboard]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -67,19 +67,19 @@ function useDashboard(dashboardData) {
|
|||||||
|
|
||||||
const updateDashboard = useCallback(
|
const updateDashboard = useCallback(
|
||||||
(data, includeVersion = true) => {
|
(data, includeVersion = true) => {
|
||||||
setDashboard(currentDashboard => extend({}, currentDashboard, data));
|
setDashboard((currentDashboard) => extend({}, currentDashboard, data));
|
||||||
data = { ...data, id: dashboard.id };
|
data = { ...data, id: dashboard.id };
|
||||||
if (includeVersion) {
|
if (includeVersion) {
|
||||||
data = { ...data, version: dashboard.version };
|
data = { ...data, version: dashboard.version };
|
||||||
}
|
}
|
||||||
return Dashboard.save(data)
|
return Dashboard.save(data)
|
||||||
.then(updatedDashboard => {
|
.then((updatedDashboard) => {
|
||||||
setDashboard(currentDashboard => extend({}, currentDashboard, pick(updatedDashboard, keys(data))));
|
setDashboard((currentDashboard) => extend({}, currentDashboard, pick(updatedDashboard, keys(data))));
|
||||||
if (has(data, "name")) {
|
if (has(data, "name")) {
|
||||||
location.setPath(url.parse(updatedDashboard.url).pathname, true);
|
location.setPath(url.parse(updatedDashboard.url).pathname, true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
const status = get(error, "response.status");
|
const status = get(error, "response.status");
|
||||||
if (status === 403) {
|
if (status === 403) {
|
||||||
notification.error("Dashboard update failed", "Permission Denied.");
|
notification.error("Dashboard update failed", "Permission Denied.");
|
||||||
@@ -102,25 +102,25 @@ function useDashboard(dashboardData) {
|
|||||||
|
|
||||||
const loadWidget = useCallback((widget, forceRefresh = false) => {
|
const loadWidget = useCallback((widget, forceRefresh = false) => {
|
||||||
widget.getParametersDefs(); // Force widget to read parameters values from URL
|
widget.getParametersDefs(); // Force widget to read parameters values from URL
|
||||||
setDashboard(currentDashboard => extend({}, currentDashboard));
|
setDashboard((currentDashboard) => extend({}, currentDashboard));
|
||||||
return widget
|
return widget
|
||||||
.load(forceRefresh)
|
.load(forceRefresh)
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
// QueryResultErrors are expected
|
// QueryResultErrors are expected
|
||||||
if (error instanceof QueryResultError) {
|
if (error instanceof QueryResultError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
})
|
})
|
||||||
.finally(() => setDashboard(currentDashboard => extend({}, currentDashboard)));
|
.finally(() => setDashboard((currentDashboard) => extend({}, currentDashboard)));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const refreshWidget = useCallback(widget => loadWidget(widget, true), [loadWidget]);
|
const refreshWidget = useCallback((widget) => loadWidget(widget, true), [loadWidget]);
|
||||||
|
|
||||||
const removeWidget = useCallback(widgetId => {
|
const removeWidget = useCallback((widgetId) => {
|
||||||
setDashboard(currentDashboard =>
|
setDashboard((currentDashboard) =>
|
||||||
extend({}, currentDashboard, {
|
extend({}, currentDashboard, {
|
||||||
widgets: currentDashboard.widgets.filter(widget => widget.id !== undefined && widget.id !== widgetId),
|
widgets: currentDashboard.widgets.filter((widget) => widget.id !== undefined && widget.id !== widgetId),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -132,11 +132,11 @@ function useDashboard(dashboardData) {
|
|||||||
(forceRefresh = false, updatedParameters = []) => {
|
(forceRefresh = false, updatedParameters = []) => {
|
||||||
const affectedWidgets = getAffectedWidgets(dashboardRef.current.widgets, updatedParameters);
|
const affectedWidgets = getAffectedWidgets(dashboardRef.current.widgets, updatedParameters);
|
||||||
const loadWidgetPromises = compact(
|
const loadWidgetPromises = compact(
|
||||||
affectedWidgets.map(widget => loadWidget(widget, forceRefresh).catch(error => error))
|
affectedWidgets.map((widget) => loadWidget(widget, forceRefresh).catch((error) => error))
|
||||||
);
|
);
|
||||||
|
|
||||||
return Promise.all(loadWidgetPromises).then(() => {
|
return Promise.all(loadWidgetPromises).then(() => {
|
||||||
const queryResults = compact(map(dashboardRef.current.widgets, widget => widget.getQueryResult()));
|
const queryResults = compact(map(dashboardRef.current.widgets, (widget) => widget.getQueryResult()));
|
||||||
const updatedFilters = collectDashboardFilters(dashboardRef.current, queryResults, location.search);
|
const updatedFilters = collectDashboardFilters(dashboardRef.current, queryResults, location.search);
|
||||||
setFilters(updatedFilters);
|
setFilters(updatedFilters);
|
||||||
});
|
});
|
||||||
@@ -145,7 +145,7 @@ function useDashboard(dashboardData) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const refreshDashboard = useCallback(
|
const refreshDashboard = useCallback(
|
||||||
updatedParameters => {
|
(updatedParameters) => {
|
||||||
if (!refreshing) {
|
if (!refreshing) {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
loadDashboard(true, updatedParameters).finally(() => setRefreshing(false));
|
loadDashboard(true, updatedParameters).finally(() => setRefreshing(false));
|
||||||
@@ -154,15 +154,30 @@ function useDashboard(dashboardData) {
|
|||||||
[refreshing, loadDashboard]
|
[refreshing, loadDashboard]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const saveDashboardParameters = useCallback(() => {
|
||||||
|
const currentDashboard = dashboardRef.current;
|
||||||
|
|
||||||
|
return updateDashboard({
|
||||||
|
options: {
|
||||||
|
...currentDashboard.options,
|
||||||
|
parameters: map(globalParameters, (p) => p.toSaveableObject()),
|
||||||
|
},
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error("Failed to persist parameter values:", error);
|
||||||
|
notification.error("Parameter values could not be saved. Your changes may not be persisted.");
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}, [globalParameters, updateDashboard]);
|
||||||
|
|
||||||
const archiveDashboard = useCallback(() => {
|
const archiveDashboard = useCallback(() => {
|
||||||
recordEvent("archive", "dashboard", dashboard.id);
|
recordEvent("archive", "dashboard", dashboard.id);
|
||||||
Dashboard.delete(dashboard).then(updatedDashboard =>
|
Dashboard.delete(dashboard).then((updatedDashboard) =>
|
||||||
setDashboard(currentDashboard => extend({}, currentDashboard, pick(updatedDashboard, ["is_archived"])))
|
setDashboard((currentDashboard) => extend({}, currentDashboard, pick(updatedDashboard, ["is_archived"])))
|
||||||
);
|
);
|
||||||
}, [dashboard]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [dashboard]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const showShareDashboardDialog = useCallback(() => {
|
const showShareDashboardDialog = useCallback(() => {
|
||||||
const handleDialogClose = () => setDashboard(currentDashboard => extend({}, currentDashboard));
|
const handleDialogClose = () => setDashboard((currentDashboard) => extend({}, currentDashboard));
|
||||||
|
|
||||||
ShareDashboardDialog.showModal({
|
ShareDashboardDialog.showModal({
|
||||||
dashboard,
|
dashboard,
|
||||||
@@ -175,8 +190,8 @@ function useDashboard(dashboardData) {
|
|||||||
const showAddTextboxDialog = useCallback(() => {
|
const showAddTextboxDialog = useCallback(() => {
|
||||||
TextboxDialog.showModal({
|
TextboxDialog.showModal({
|
||||||
isNew: true,
|
isNew: true,
|
||||||
}).onClose(text =>
|
}).onClose((text) =>
|
||||||
dashboard.addWidget(text).then(() => setDashboard(currentDashboard => extend({}, currentDashboard)))
|
dashboard.addWidget(text).then(() => setDashboard((currentDashboard) => extend({}, currentDashboard)))
|
||||||
);
|
);
|
||||||
}, [dashboard]);
|
}, [dashboard]);
|
||||||
|
|
||||||
@@ -188,13 +203,13 @@ function useDashboard(dashboardData) {
|
|||||||
.addWidget(visualization, {
|
.addWidget(visualization, {
|
||||||
parameterMappings: editableMappingsToParameterMappings(parameterMappings),
|
parameterMappings: editableMappingsToParameterMappings(parameterMappings),
|
||||||
})
|
})
|
||||||
.then(widget => {
|
.then((widget) => {
|
||||||
const widgetsToSave = [
|
const widgetsToSave = [
|
||||||
widget,
|
widget,
|
||||||
...synchronizeWidgetTitles(widget.options.parameterMappings, dashboard.widgets),
|
...synchronizeWidgetTitles(widget.options.parameterMappings, dashboard.widgets),
|
||||||
];
|
];
|
||||||
return Promise.all(widgetsToSave.map(w => w.save())).then(() =>
|
return Promise.all(widgetsToSave.map((w) => w.save())).then(() =>
|
||||||
setDashboard(currentDashboard => extend({}, currentDashboard))
|
setDashboard((currentDashboard) => extend({}, currentDashboard))
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -238,6 +253,7 @@ function useDashboard(dashboardData) {
|
|||||||
setRefreshRate,
|
setRefreshRate,
|
||||||
disableRefreshRate,
|
disableRefreshRate,
|
||||||
...editModeHandler,
|
...editModeHandler,
|
||||||
|
saveDashboardParameters,
|
||||||
gridDisabled,
|
gridDisabled,
|
||||||
setGridDisabled,
|
setGridDisabled,
|
||||||
fullscreen,
|
fullscreen,
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ export const urlForDashboard = ({ id, slug }) => `dashboards/${id}-${slug}`;
|
|||||||
|
|
||||||
export function collectDashboardFilters(dashboard, queryResults, urlParams) {
|
export function collectDashboardFilters(dashboard, queryResults, urlParams) {
|
||||||
const filters = {};
|
const filters = {};
|
||||||
_.each(queryResults, queryResult => {
|
_.each(queryResults, (queryResult) => {
|
||||||
const queryFilters = queryResult && queryResult.getFilters ? queryResult.getFilters() : [];
|
const queryFilters = queryResult && queryResult.getFilters ? queryResult.getFilters() : [];
|
||||||
_.each(queryFilters, queryFilter => {
|
_.each(queryFilters, (queryFilter) => {
|
||||||
const hasQueryStringValue = _.has(urlParams, queryFilter.name);
|
const hasQueryStringValue = _.has(urlParams, queryFilter.name);
|
||||||
|
|
||||||
if (!(hasQueryStringValue || dashboard.dashboard_filters_enabled)) {
|
if (!(hasQueryStringValue || dashboard.dashboard_filters_enabled)) {
|
||||||
@@ -44,7 +44,7 @@ function prepareWidgetsForDashboard(widgets) {
|
|||||||
const defaultWidgetSizeY =
|
const defaultWidgetSizeY =
|
||||||
Math.max(
|
Math.max(
|
||||||
_.chain(widgets)
|
_.chain(widgets)
|
||||||
.map(w => w.options.position.sizeY)
|
.map((w) => w.options.position.sizeY)
|
||||||
.max()
|
.max()
|
||||||
.value(),
|
.value(),
|
||||||
20
|
20
|
||||||
@@ -55,11 +55,11 @@ function prepareWidgetsForDashboard(widgets) {
|
|||||||
// 2. update position of widgets in each row - place it right below
|
// 2. update position of widgets in each row - place it right below
|
||||||
// biggest widget from previous row
|
// biggest widget from previous row
|
||||||
_.chain(widgets)
|
_.chain(widgets)
|
||||||
.sortBy(widget => widget.options.position.row)
|
.sortBy((widget) => widget.options.position.row)
|
||||||
.groupBy(widget => widget.options.position.row)
|
.groupBy((widget) => widget.options.position.row)
|
||||||
.reduce((row, widgetsAtRow) => {
|
.reduce((row, widgetsAtRow) => {
|
||||||
let height = 1;
|
let height = 1;
|
||||||
_.each(widgetsAtRow, widget => {
|
_.each(widgetsAtRow, (widget) => {
|
||||||
height = Math.max(
|
height = Math.max(
|
||||||
height,
|
height,
|
||||||
widget.options.position.autoHeight ? defaultWidgetSizeY : widget.options.position.sizeY
|
widget.options.position.autoHeight ? defaultWidgetSizeY : widget.options.position.sizeY
|
||||||
@@ -74,8 +74,8 @@ function prepareWidgetsForDashboard(widgets) {
|
|||||||
.value();
|
.value();
|
||||||
|
|
||||||
// Sort widgets by updated column and row value
|
// Sort widgets by updated column and row value
|
||||||
widgets = _.sortBy(widgets, widget => widget.options.position.col);
|
widgets = _.sortBy(widgets, (widget) => widget.options.position.col);
|
||||||
widgets = _.sortBy(widgets, widget => widget.options.position.row);
|
widgets = _.sortBy(widgets, (widget) => widget.options.position.row);
|
||||||
|
|
||||||
return widgets;
|
return widgets;
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ function calculateNewWidgetPosition(existingWidgets, newWidget) {
|
|||||||
|
|
||||||
// Find first free row for each column
|
// Find first free row for each column
|
||||||
const bottomLine = _.chain(existingWidgets)
|
const bottomLine = _.chain(existingWidgets)
|
||||||
.map(w => {
|
.map((w) => {
|
||||||
const options = _.extend({}, w.options);
|
const options = _.extend({}, w.options);
|
||||||
const position = _.extend({ row: 0, sizeY: 0 }, options.position);
|
const position = _.extend({ row: 0, sizeY: 0 }, options.position);
|
||||||
return {
|
return {
|
||||||
@@ -97,21 +97,24 @@ function calculateNewWidgetPosition(existingWidgets, newWidget) {
|
|||||||
height: position.sizeY,
|
height: position.sizeY,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.reduce((result, item) => {
|
.reduce(
|
||||||
const from = Math.max(item.left, 0);
|
(result, item) => {
|
||||||
const to = Math.min(item.right, result.length + 1);
|
const from = Math.max(item.left, 0);
|
||||||
for (let i = from; i < to; i += 1) {
|
const to = Math.min(item.right, result.length + 1);
|
||||||
result[i] = Math.max(result[i], item.bottom);
|
for (let i = from; i < to; i += 1) {
|
||||||
}
|
result[i] = Math.max(result[i], item.bottom);
|
||||||
return result;
|
}
|
||||||
}, _.map(new Array(dashboardGridOptions.columns), _.constant(0)))
|
return result;
|
||||||
|
},
|
||||||
|
_.map(new Array(dashboardGridOptions.columns), _.constant(0))
|
||||||
|
)
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
// Go through columns, pick them by count necessary to hold new block,
|
// Go through columns, pick them by count necessary to hold new block,
|
||||||
// and calculate bottom-most free row per group.
|
// and calculate bottom-most free row per group.
|
||||||
// Choose group with the top-most free row (comparing to other groups)
|
// Choose group with the top-most free row (comparing to other groups)
|
||||||
return _.chain(_.range(0, dashboardGridOptions.columns - width + 1))
|
return _.chain(_.range(0, dashboardGridOptions.columns - width + 1))
|
||||||
.map(col => ({
|
.map((col) => ({
|
||||||
col,
|
col,
|
||||||
row: _.chain(bottomLine)
|
row: _.chain(bottomLine)
|
||||||
.slice(col, col + width)
|
.slice(col, col + width)
|
||||||
@@ -126,14 +129,14 @@ function calculateNewWidgetPosition(existingWidgets, newWidget) {
|
|||||||
export function Dashboard(dashboard) {
|
export function Dashboard(dashboard) {
|
||||||
_.extend(this, dashboard);
|
_.extend(this, dashboard);
|
||||||
Object.defineProperty(this, "url", {
|
Object.defineProperty(this, "url", {
|
||||||
get: function() {
|
get: function () {
|
||||||
return urlForDashboard(this);
|
return urlForDashboard(this);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareDashboardWidgets(widgets) {
|
function prepareDashboardWidgets(widgets) {
|
||||||
return prepareWidgetsForDashboard(_.map(widgets, widget => new Widget(widget)));
|
return prepareWidgetsForDashboard(_.map(widgets, (widget) => new Widget(widget)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformSingle(dashboard) {
|
function transformSingle(dashboard) {
|
||||||
@@ -154,7 +157,7 @@ function transformResponse(data) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveOrCreateUrl = data => (data.id ? `api/dashboards/${data.id}` : "api/dashboards");
|
const saveOrCreateUrl = (data) => (data.id ? `api/dashboards/${data.id}` : "api/dashboards");
|
||||||
const DashboardService = {
|
const DashboardService = {
|
||||||
get: ({ id, slug }) => {
|
get: ({ id, slug }) => {
|
||||||
const params = {};
|
const params = {};
|
||||||
@@ -164,12 +167,12 @@ const DashboardService = {
|
|||||||
return axios.get(`api/dashboards/${id || slug}`, { params }).then(transformResponse);
|
return axios.get(`api/dashboards/${id || slug}`, { params }).then(transformResponse);
|
||||||
},
|
},
|
||||||
getByToken: ({ token }) => axios.get(`api/dashboards/public/${token}`).then(transformResponse),
|
getByToken: ({ token }) => axios.get(`api/dashboards/public/${token}`).then(transformResponse),
|
||||||
save: data => axios.post(saveOrCreateUrl(data), data).then(transformResponse),
|
save: (data) => axios.post(saveOrCreateUrl(data), data).then(transformResponse),
|
||||||
delete: ({ id }) => axios.delete(`api/dashboards/${id}`).then(transformResponse),
|
delete: ({ id }) => axios.delete(`api/dashboards/${id}`).then(transformResponse),
|
||||||
query: params => axios.get("api/dashboards", { params }).then(transformResponse),
|
query: (params) => axios.get("api/dashboards", { params }).then(transformResponse),
|
||||||
recent: params => axios.get("api/dashboards/recent", { params }).then(transformResponse),
|
recent: (params) => axios.get("api/dashboards/recent", { params }).then(transformResponse),
|
||||||
myDashboards: params => axios.get("api/dashboards/my", { params }).then(transformResponse),
|
myDashboards: (params) => axios.get("api/dashboards/my", { params }).then(transformResponse),
|
||||||
favorites: params => axios.get("api/dashboards/favorites", { params }).then(transformResponse),
|
favorites: (params) => axios.get("api/dashboards/favorites", { params }).then(transformResponse),
|
||||||
favorite: ({ id }) => axios.post(`api/dashboards/${id}/favorite`),
|
favorite: ({ id }) => axios.post(`api/dashboards/${id}/favorite`),
|
||||||
unfavorite: ({ id }) => axios.delete(`api/dashboards/${id}/favorite`),
|
unfavorite: ({ id }) => axios.delete(`api/dashboards/${id}/favorite`),
|
||||||
fork: ({ id }) => axios.post(`api/dashboards/${id}/fork`, { id }).then(transformResponse),
|
fork: ({ id }) => axios.post(`api/dashboards/${id}/fork`, { id }).then(transformResponse),
|
||||||
@@ -187,13 +190,13 @@ Dashboard.prototype.canEdit = function canEdit() {
|
|||||||
Dashboard.prototype.getParametersDefs = function getParametersDefs() {
|
Dashboard.prototype.getParametersDefs = function getParametersDefs() {
|
||||||
const globalParams = {};
|
const globalParams = {};
|
||||||
const queryParams = location.search;
|
const queryParams = location.search;
|
||||||
_.each(this.widgets, widget => {
|
_.each(this.widgets, (widget) => {
|
||||||
if (widget.getQuery()) {
|
if (widget.getQuery()) {
|
||||||
const mappings = widget.getParameterMappings();
|
const mappings = widget.getParameterMappings();
|
||||||
widget
|
widget
|
||||||
.getQuery()
|
.getQuery()
|
||||||
.getParametersDefs(false)
|
.getParametersDefs(false)
|
||||||
.forEach(param => {
|
.forEach((param) => {
|
||||||
const mapping = mappings[param.name];
|
const mapping = mappings[param.name];
|
||||||
if (mapping.type === Widget.MappingType.DashboardLevel) {
|
if (mapping.type === Widget.MappingType.DashboardLevel) {
|
||||||
// create global param
|
// create global param
|
||||||
@@ -210,15 +213,19 @@ Dashboard.prototype.getParametersDefs = function getParametersDefs() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const mergedValues = {
|
||||||
|
..._.mapValues(globalParams, (p) => p.value),
|
||||||
|
...Object.fromEntries((this.options.parameters || []).map((param) => [param.name, param.value])),
|
||||||
|
};
|
||||||
const resultingGlobalParams = _.values(
|
const resultingGlobalParams = _.values(
|
||||||
_.each(globalParams, param => {
|
_.each(globalParams, (param) => {
|
||||||
param.setValue(param.value); // apply global param value to all locals
|
param.setValue(mergedValues[param.name]); // apply merged value
|
||||||
param.fromUrlParams(queryParams); // try to initialize from url (may do nothing)
|
param.fromUrlParams(queryParams); // allow param-specific parsing logic
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// order dashboard params using paramOrder
|
// order dashboard params using paramOrder
|
||||||
return _.sortBy(resultingGlobalParams, param =>
|
return _.sortBy(resultingGlobalParams, (param) =>
|
||||||
_.includes(this.options.globalParamOrder, param.name)
|
_.includes(this.options.globalParamOrder, param.name)
|
||||||
? _.indexOf(this.options.globalParamOrder, param.name)
|
? _.indexOf(this.options.globalParamOrder, param.name)
|
||||||
: _.size(this.options.globalParamOrder)
|
: _.size(this.options.globalParamOrder)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class Parameter {
|
|||||||
|
|
||||||
updateLocals() {
|
updateLocals() {
|
||||||
if (isArray(this.locals)) {
|
if (isArray(this.locals)) {
|
||||||
each(this.locals, local => {
|
each(this.locals, (local) => {
|
||||||
local.setValue(this.value);
|
local.setValue(this.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ class Parameter {
|
|||||||
|
|
||||||
/** Get a saveable version of the Parameter by omitting unnecessary props */
|
/** Get a saveable version of the Parameter by omitting unnecessary props */
|
||||||
toSaveableObject() {
|
toSaveableObject() {
|
||||||
return omit(this, ["$$value", "urlPrefix", "pendingValue", "parentQueryId"]);
|
return omit(this, ["$$value", "urlPrefix", "pendingValue", "parentQueryId", "locals"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user