addWidget.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import {useCallback, useMemo} from 'react';
  2. import {useSortable} from '@dnd-kit/sortable';
  3. import styled from '@emotion/styled';
  4. import Feature from 'sentry/components/acl/feature';
  5. import type {ButtonProps} from 'sentry/components/button';
  6. import {Button} from 'sentry/components/button';
  7. import DropdownButton from 'sentry/components/dropdownButton';
  8. import type {MenuItemProps} from 'sentry/components/dropdownMenu';
  9. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  10. import ExternalLink from 'sentry/components/links/externalLink';
  11. import {IconAdd} from 'sentry/icons';
  12. import {t} from 'sentry/locale';
  13. import {space} from 'sentry/styles/space';
  14. import {trackAnalytics} from 'sentry/utils/analytics';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import {DataSet} from 'sentry/views/dashboards/widgetBuilder/utils';
  17. import {DisplayType} from './types';
  18. import WidgetWrapper from './widgetWrapper';
  19. export const ADD_WIDGET_BUTTON_DRAG_ID = 'add-widget-button';
  20. const initialStyles = {
  21. x: 0,
  22. y: 0,
  23. scaleX: 1,
  24. scaleY: 1,
  25. };
  26. type Props = {
  27. onAddWidget: (dataset: DataSet) => void;
  28. onAddWidgetFromNewWidgetBuilder?: (
  29. dataset: DataSet,
  30. openWidgetTemplates?: boolean
  31. ) => void;
  32. };
  33. function AddWidget({onAddWidget, onAddWidgetFromNewWidgetBuilder}: Props) {
  34. const {setNodeRef, transform} = useSortable({
  35. disabled: true,
  36. id: ADD_WIDGET_BUTTON_DRAG_ID,
  37. transition: null,
  38. });
  39. const organization = useOrganization();
  40. const defaultDataset = organization.features.includes(
  41. 'performance-discover-dataset-selector'
  42. )
  43. ? DataSet.ERRORS
  44. : DataSet.EVENTS;
  45. const addWidgetDropdownItems: MenuItemProps[] = [
  46. {
  47. key: 'create-custom-widget',
  48. label: t('Create Custom Widget'),
  49. onAction: () => onAddWidgetFromNewWidgetBuilder?.(defaultDataset, false),
  50. },
  51. {
  52. key: 'from-widget-library',
  53. label: t('From Widget Library'),
  54. onAction: () => onAddWidgetFromNewWidgetBuilder?.(defaultDataset, true),
  55. },
  56. ];
  57. return (
  58. <Feature features="dashboards-edit">
  59. <WidgetWrapper
  60. key="add"
  61. ref={setNodeRef}
  62. displayType={DisplayType.BIG_NUMBER}
  63. layoutId={ADD_WIDGET_BUTTON_DRAG_ID}
  64. style={{originX: 0, originY: 0}}
  65. animate={
  66. transform
  67. ? {
  68. x: transform.x,
  69. y: transform.y,
  70. scaleX: transform?.scaleX && transform.scaleX <= 1 ? transform.scaleX : 1,
  71. scaleY: transform?.scaleY && transform.scaleY <= 1 ? transform.scaleY : 1,
  72. }
  73. : initialStyles
  74. }
  75. transition={{
  76. duration: 0.25,
  77. }}
  78. >
  79. {organization.features.includes('dashboards-widget-builder-redesign') ? (
  80. <InnerWrapper onClick={() => onAddWidgetFromNewWidgetBuilder?.(defaultDataset)}>
  81. <DropdownMenu
  82. items={addWidgetDropdownItems}
  83. data-test-id="widget-add"
  84. triggerProps={{
  85. 'aria-label': t('Add Widget'),
  86. size: 'md',
  87. showChevron: false,
  88. icon: <IconAdd isCircled size="lg" color="inactive" />,
  89. borderless: true,
  90. }}
  91. />
  92. </InnerWrapper>
  93. ) : (
  94. <InnerWrapper onClick={() => onAddWidget(defaultDataset)}>
  95. <AddButton
  96. data-test-id="widget-add"
  97. icon={<IconAdd size="lg" isCircled color="inactive" />}
  98. aria-label={t('Add widget')}
  99. />
  100. </InnerWrapper>
  101. )}
  102. </WidgetWrapper>
  103. </Feature>
  104. );
  105. }
  106. const AddButton = styled(Button)`
  107. border: none;
  108. &,
  109. &:focus,
  110. &:active,
  111. &:hover {
  112. background: transparent;
  113. box-shadow: none;
  114. }
  115. `;
  116. export default AddWidget;
  117. export function AddWidgetButton({onAddWidget, ...buttonProps}: Props & ButtonProps) {
  118. const organization = useOrganization();
  119. const handleAction = useCallback(
  120. (dataset: DataSet) => {
  121. trackAnalytics('dashboards_views.widget_library.opened', {
  122. organization,
  123. });
  124. onAddWidget(dataset);
  125. },
  126. [organization, onAddWidget]
  127. );
  128. const items = useMemo(() => {
  129. const menuItems: MenuItemProps[] = [];
  130. if (organization.features.includes('performance-discover-dataset-selector')) {
  131. menuItems.push({
  132. key: DataSet.ERRORS,
  133. label: t('Errors'),
  134. onAction: () => handleAction(DataSet.ERRORS),
  135. });
  136. menuItems.push({
  137. key: DataSet.TRANSACTIONS,
  138. label: t('Transactions'),
  139. onAction: () => handleAction(DataSet.TRANSACTIONS),
  140. });
  141. } else {
  142. menuItems.push({
  143. key: DataSet.EVENTS,
  144. label: t('Errors and Transactions'),
  145. onAction: () => handleAction(DataSet.EVENTS),
  146. });
  147. }
  148. menuItems.push({
  149. key: DataSet.ISSUES,
  150. label: t('Issues'),
  151. details: t('States, Assignment, Time, etc.'),
  152. onAction: () => handleAction(DataSet.ISSUES),
  153. });
  154. menuItems.push({
  155. key: DataSet.RELEASES,
  156. label: t('Releases'),
  157. details: t('Sessions, Crash rates, etc.'),
  158. onAction: () => handleAction(DataSet.RELEASES),
  159. });
  160. return menuItems;
  161. }, [handleAction, organization]);
  162. return (
  163. <DropdownMenu
  164. items={items}
  165. trigger={triggerProps => (
  166. <DropdownButton
  167. {...triggerProps}
  168. {...buttonProps}
  169. data-test-id="widget-add"
  170. size="sm"
  171. icon={<IconAdd isCircled />}
  172. >
  173. {t('Add Widget')}
  174. </DropdownButton>
  175. )}
  176. menuTitle={
  177. <MenuTitle>
  178. {t('Dataset')}
  179. <ExternalLink href="https://docs.sentry.io/product/dashboards/widget-builder/#choose-your-dataset">
  180. {t('Learn more')}
  181. </ExternalLink>
  182. </MenuTitle>
  183. }
  184. />
  185. );
  186. }
  187. const InnerWrapper = styled('div')<{onClick?: () => void}>`
  188. width: 100%;
  189. height: 110px;
  190. border: 2px dashed ${p => p.theme.border};
  191. border-radius: ${p => p.theme.borderRadius};
  192. display: flex;
  193. align-items: center;
  194. justify-content: center;
  195. cursor: ${p => (p.onClick ? 'pointer' : '')};
  196. `;
  197. const MenuTitle = styled('span')`
  198. display: flex;
  199. gap: ${space(1)};
  200. & > a {
  201. font-weight: ${p => p.theme.fontWeightNormal};
  202. }
  203. `;