123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- import {Component} from 'react';
- import {InjectedRouter} from 'react-router/lib/Router';
- import {closestCenter, DndContext} from '@dnd-kit/core';
- import {arrayMove, rectSortingStrategy, SortableContext} from '@dnd-kit/sortable';
- import styled from '@emotion/styled';
- import {Location} from 'history';
- import {openAddDashboardWidgetModal} from 'app/actionCreators/modal';
- import {loadOrganizationTags} from 'app/actionCreators/tags';
- import {Client} from 'app/api';
- import space from 'app/styles/space';
- import {GlobalSelection, Organization} from 'app/types';
- import withApi from 'app/utils/withApi';
- import withGlobalSelection from 'app/utils/withGlobalSelection';
- import {DataSet} from './widget/utils';
- import AddWidget, {ADD_WIDGET_BUTTON_DRAG_ID} from './addWidget';
- import SortableWidget from './sortableWidget';
- import {DashboardDetails, Widget} from './types';
- type Props = {
- api: Client;
- organization: Organization;
- dashboard: DashboardDetails;
- selection: GlobalSelection;
- isEditing: boolean;
- router: InjectedRouter;
- location: Location;
- /**
- * Fired when widgets are added/removed/sorted.
- */
- onUpdate: (widgets: Widget[]) => void;
- onSetWidgetToBeUpdated: (widget: Widget) => void;
- paramDashboardId?: string;
- };
- class Dashboard extends Component<Props> {
- componentDidMount() {
- const {isEditing} = this.props;
- // Load organization tags when in edit mode.
- if (isEditing) {
- this.fetchTags();
- }
- }
- componentDidUpdate(prevProps: Props) {
- const {isEditing} = this.props;
- // Load organization tags when going into edit mode.
- // We use tags on the add widget modal.
- if (prevProps.isEditing !== isEditing && isEditing) {
- this.fetchTags();
- }
- }
- fetchTags() {
- const {api, organization, selection} = this.props;
- loadOrganizationTags(api, organization.slug, selection);
- }
- handleStartAdd = () => {
- const {organization, dashboard, selection} = this.props;
- openAddDashboardWidgetModal({
- organization,
- dashboard,
- selection,
- onAddWidget: this.handleAddComplete,
- });
- };
- handleOpenWidgetBuilder = () => {
- const {router, paramDashboardId, organization, location} = this.props;
- if (paramDashboardId) {
- router.push({
- pathname: `/organizations/${organization.slug}/dashboard/${paramDashboardId}/widget/new/`,
- query: {
- ...location.query,
- dataSet: DataSet.EVENTS,
- },
- });
- return;
- }
- router.push({
- pathname: `/organizations/${organization.slug}/dashboards/new/widget/new/`,
- query: {
- ...location.query,
- dataSet: DataSet.EVENTS,
- },
- });
- };
- handleAddComplete = (widget: Widget) => {
- this.props.onUpdate([...this.props.dashboard.widgets, widget]);
- };
- handleUpdateComplete = (index: number) => (nextWidget: Widget) => {
- const nextList = [...this.props.dashboard.widgets];
- nextList[index] = nextWidget;
- this.props.onUpdate(nextList);
- };
- handleDeleteWidget = (index: number) => () => {
- const nextList = [...this.props.dashboard.widgets];
- nextList.splice(index, 1);
- this.props.onUpdate(nextList);
- };
- handleEditWidget = (widget: Widget, index: number) => () => {
- const {
- organization,
- dashboard,
- selection,
- router,
- location,
- paramDashboardId,
- onSetWidgetToBeUpdated,
- } = this.props;
- if (organization.features.includes('metrics')) {
- onSetWidgetToBeUpdated(widget);
- if (paramDashboardId) {
- router.push({
- pathname: `/organizations/${organization.slug}/dashboard/${paramDashboardId}/widget/${index}/edit/`,
- query: {
- ...location.query,
- dataSet: DataSet.EVENTS,
- },
- });
- return;
- }
- router.push({
- pathname: `/organizations/${organization.slug}/dashboards/new/widget/${index}/edit/`,
- query: {
- ...location.query,
- dataSet: DataSet.EVENTS,
- },
- });
- }
- openAddDashboardWidgetModal({
- organization,
- dashboard,
- widget,
- selection,
- onAddWidget: this.handleAddComplete,
- onUpdateWidget: this.handleUpdateComplete(index),
- });
- };
- getWidgetIds() {
- return [
- ...this.props.dashboard.widgets.map((widget, index): string => {
- return generateWidgetId(widget, index);
- }),
- ADD_WIDGET_BUTTON_DRAG_ID,
- ];
- }
- renderWidget(widget: Widget, index: number) {
- const {isEditing} = this.props;
- const key = generateWidgetId(widget, index);
- const dragId = key;
- return (
- <SortableWidget
- key={key}
- widget={widget}
- dragId={dragId}
- isEditing={isEditing}
- onDelete={this.handleDeleteWidget(index)}
- onEdit={this.handleEditWidget(widget, index)}
- />
- );
- }
- render() {
- const {
- isEditing,
- onUpdate,
- dashboard: {widgets},
- organization,
- } = this.props;
- const items = this.getWidgetIds();
- return (
- <DndContext
- collisionDetection={closestCenter}
- onDragEnd={({over, active}) => {
- const activeDragId = active.id;
- const getIndex = items.indexOf.bind(items);
- const activeIndex = activeDragId ? getIndex(activeDragId) : -1;
- if (over && over.id !== ADD_WIDGET_BUTTON_DRAG_ID) {
- const overIndex = getIndex(over.id);
- if (activeIndex !== overIndex) {
- onUpdate(arrayMove(widgets, activeIndex, overIndex));
- }
- }
- }}
- >
- <WidgetContainer>
- <SortableContext items={items} strategy={rectSortingStrategy}>
- {widgets.map((widget, index) => this.renderWidget(widget, index))}
- {isEditing && (
- <AddWidget
- orgFeatures={organization.features}
- onAddWidget={this.handleStartAdd}
- onOpenWidgetBuilder={this.handleOpenWidgetBuilder}
- />
- )}
- </SortableContext>
- </WidgetContainer>
- </DndContext>
- );
- }
- }
- export default withApi(withGlobalSelection(Dashboard));
- const WidgetContainer = styled('div')`
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- grid-auto-flow: row dense;
- grid-gap: ${space(2)};
- @media (min-width: ${p => p.theme.breakpoints[1]}) {
- grid-template-columns: repeat(4, minmax(0, 1fr));
- }
- @media (min-width: ${p => p.theme.breakpoints[3]}) {
- grid-template-columns: repeat(6, minmax(0, 1fr));
- }
- @media (min-width: ${p => p.theme.breakpoints[4]}) {
- grid-template-columns: repeat(8, minmax(0, 1fr));
- }
- `;
- function generateWidgetId(widget: Widget, index: number) {
- return widget.id ? `${widget.id}-index-${index}` : `index-${index}`;
- }
|