123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- import type {Location} from 'history';
- import type {InjectedRouter} from 'sentry/types/legacyReactRouter';
- import type {Organization} from 'sentry/types/organization';
- import {decodeList} from 'sentry/utils/queryString';
- import WidgetLegendNameEncoderDecoder from 'sentry/views/dashboards/widgetLegendNameEncoderDecoder';
- import {type DashboardDetails, DisplayType, type Widget} from './types';
- type Props = {
- dashboard: DashboardDetails | null;
- location: Location;
- organization: Organization;
- router: InjectedRouter;
- };
- type LegendSelection = Record<string, boolean>;
- const SERIES_LIST_DELIMITER = ',';
- const WIDGET_ID_DELIMITER = ':';
- const SERIES_NAME_DELIMITER = ';';
- class WidgetLegendSelectionState {
- dashboard: DashboardDetails | null;
- location: Location;
- organization: Organization;
- router: InjectedRouter;
- constructor(props: Props) {
- this.dashboard = props.dashboard;
- this.location = props.location;
- this.organization = props.organization;
- this.router = props.router;
- }
- // Updates legend param when a legend selection has been changed
- setWidgetSelectionState(selected: LegendSelection, widget: Widget) {
- const [dashboard, location, router] = [this.dashboard, this.location, this.router];
- const widgets = dashboard ? dashboard.widgets : [];
- let newLegendQuery: string[];
- if (!location.query.unselectedSeries && widgets) {
- newLegendQuery = widgets
- .filter(dashboardWidget => this.widgetIsChart(dashboardWidget))
- .map(dashboardWidget => {
- return dashboardWidget.id === widget.id
- ? this.encodeLegendQueryParam(widget, selected)
- : this.formatLegendDefaultQuery(dashboardWidget);
- })
- .filter(unselectedSeries => unselectedSeries !== undefined);
- const thisWidgetWithReleasesWasSelected =
- Object.values(selected).filter(value => value === false).length !== 1 &&
- Object.keys(selected).includes(`Releases${SERIES_NAME_DELIMITER}${widget.id}`);
- const thisWidgetWithoutReleasesWasSelected =
- !Object.keys(selected).includes(`Releases${SERIES_NAME_DELIMITER}${widget.id}`) &&
- Object.values(selected).filter(value => value === false).length === 1;
- if (thisWidgetWithReleasesWasSelected || thisWidgetWithoutReleasesWasSelected) {
- router.replace({
- query: {
- ...location.query,
- unselectedSeries: newLegendQuery,
- },
- });
- }
- } else if (Array.isArray(location.query.unselectedSeries)) {
- let isInQuery = false;
- newLegendQuery = location.query.unselectedSeries.map(widgetLegend => {
- if (widgetLegend.includes(widget.id!)) {
- isInQuery = true;
- // names of legend options are stored as seriesName:widgetId and are stored in
- // query param as widgetId-seriesName-seriesName2-...
- return this.encodeLegendQueryParam(widget, selected);
- }
- return widgetLegend;
- });
- !isInQuery
- ? router.replace({
- query: {
- ...location.query,
- unselectedSeries: [
- ...location.query.unselectedSeries,
- this.encodeLegendQueryParam(widget, selected),
- ],
- },
- })
- : router.replace({
- query: {
- ...location.query,
- unselectedSeries: newLegendQuery,
- },
- });
- } else {
- if (location.query.unselectedSeries?.includes(widget.id!)) {
- router.replace({
- query: {
- ...location.query,
- unselectedSeries: [this.encodeLegendQueryParam(widget, selected)],
- },
- });
- } else {
- router.replace({
- query: {
- ...location.query,
- unselectedSeries: [
- location.query.unselectedSeries,
- this.encodeLegendQueryParam(widget, selected),
- ],
- },
- });
- }
- }
- }
- // sets unselected legend options by the legend query param
- getWidgetSelectionState(widget: Widget): LegendSelection {
- const location = this.location;
- return location.query.unselectedSeries
- ? this.decodeLegendQueryParam(widget)
- : this.widgetRequiresLegendUnselection(widget) &&
- this.organization.features.includes('dashboards-releases-on-charts')
- ? {
- [WidgetLegendNameEncoderDecoder.encodeSeriesNameForLegend(
- 'Releases',
- widget.id
- )]: false,
- }
- : {};
- }
- widgetRequiresLegendUnselection(widget: Widget) {
- return (
- widget.displayType === DisplayType.AREA || widget.displayType === DisplayType.LINE
- );
- }
- widgetIsChart(widget: Widget) {
- return (
- widget.displayType === DisplayType.AREA ||
- widget.displayType === DisplayType.LINE ||
- widget.displayType === DisplayType.BAR ||
- widget.displayType === DisplayType.TOP_N
- );
- }
- formatLegendDefaultQuery(widget: Widget) {
- return this.organization.features.includes('dashboards-releases-on-charts') &&
- this.widgetRequiresLegendUnselection(widget)
- ? `${widget.id}${WIDGET_ID_DELIMITER}Releases`
- : undefined;
- }
- // going from selected to query param
- encodeLegendQueryParam(widget: Widget, selected: LegendSelection) {
- return (
- widget.id +
- WIDGET_ID_DELIMITER +
- Object.keys(selected)
- .filter(key => !selected[key])
- .map(series =>
- encodeURIComponent(
- WidgetLegendNameEncoderDecoder.decodeSeriesNameForLegend(series)
- )
- )
- .join(SERIES_LIST_DELIMITER)
- );
- }
- // going from query param to selected
- decodeLegendQueryParam(widget: Widget) {
- const location = this.location;
- const widgetLegendString = decodeList(location.query.unselectedSeries).find(
- widgetLegend => widgetLegend.includes(widget.id!)
- );
- if (widgetLegendString) {
- const [_, seriesNameString] = widgetLegendString.split(WIDGET_ID_DELIMITER);
- const seriesNames = seriesNameString.split(SERIES_LIST_DELIMITER);
- return seriesNames.reduce((acc, series) => {
- acc[
- decodeURIComponent(
- WidgetLegendNameEncoderDecoder.encodeSeriesNameForLegend(series, widget.id)
- )
- ] = false;
- return acc;
- }, {});
- }
- return {};
- }
- // when a widget has been changed/added/deleted update legend to incorporate that
- setMultipleWidgetSelectionStateURL(newDashboard: DashboardDetails, newWidget?: Widget) {
- const location = this.location;
- if (!location.query.unselectedSeries) {
- return location.query.unselectedSeries;
- }
- // if widget was updated it returns updated widget to default selection state
- if (newWidget && newDashboard.widgets.includes(newWidget)) {
- const formattedDefaultQuery = this.formatLegendDefaultQuery(newWidget);
- const newQuery = Array.isArray(location.query.unselectedSeries)
- ? location.query.unselectedSeries
- .map(legend => {
- if (legend.includes(newWidget.id!)) {
- return this.formatLegendDefaultQuery(newWidget);
- }
- return legend;
- })
- .filter(Boolean)
- : location.query.unselectedSeries.includes(newWidget.id!)
- ? formattedDefaultQuery
- : [location.query.unselectedSeries, formattedDefaultQuery].filter(Boolean);
- return newQuery;
- }
- // if widget was deleted it removes it from the selection query (clean up the url)
- if (newWidget) {
- return Array.isArray(location.query.unselectedSeries)
- ? location.query.unselectedSeries
- .map(legend => {
- if (legend.includes(newWidget.id!)) {
- return undefined;
- }
- return legend;
- })
- .filter(Boolean)
- : location.query.unselectedSeries.includes(newWidget.id!)
- ? []
- : location.query.unselectedSeries;
- }
- // widget added (since added widgets don't have an id until submitted), it sets selection state based on all widgets
- const unselectedSeries = newDashboard.widgets
- .map(widget => {
- if (Array.isArray(location.query.unselectedSeries)) {
- const widgetLegendQuery = location.query.unselectedSeries.find(legend =>
- legend.includes(widget.id!)
- );
- if (!widgetLegendQuery && this.widgetRequiresLegendUnselection(widget)) {
- return this.formatLegendDefaultQuery(widget);
- }
- return widgetLegendQuery;
- }
- return location.query.unselectedSeries?.includes(widget.id!)
- ? location.query.unselectedSeries
- : this.formatLegendDefaultQuery(widget);
- })
- .filter(Boolean);
- return unselectedSeries;
- }
- }
- export default WidgetLegendSelectionState;
|