addWidget.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import {useSortable} from '@dnd-kit/sortable';
  2. import styled from '@emotion/styled';
  3. import {Button, ButtonProps} from 'sentry/components/button';
  4. import {CompactSelect} from 'sentry/components/compactSelect';
  5. import DropdownButton from 'sentry/components/dropdownButton';
  6. import {IconAdd} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import {trackAnalytics} from 'sentry/utils/analytics';
  9. import {hasDDMExperimentalFeature, hasDDMFeature} from 'sentry/utils/metrics/features';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import {DataSet} from 'sentry/views/dashboards/widgetBuilder/utils';
  12. import {DisplayType} from './types';
  13. import WidgetWrapper from './widgetWrapper';
  14. export const ADD_WIDGET_BUTTON_DRAG_ID = 'add-widget-button';
  15. const initialStyles = {
  16. x: 0,
  17. y: 0,
  18. scaleX: 1,
  19. scaleY: 1,
  20. };
  21. type Props = {
  22. onAddWidget: (dataset: DataSet) => void;
  23. };
  24. function AddWidget({onAddWidget}: Props) {
  25. const {setNodeRef, transform} = useSortable({
  26. disabled: true,
  27. id: ADD_WIDGET_BUTTON_DRAG_ID,
  28. transition: null,
  29. });
  30. const organization = useOrganization();
  31. return (
  32. <WidgetWrapper
  33. key="add"
  34. ref={setNodeRef}
  35. displayType={DisplayType.BIG_NUMBER}
  36. layoutId={ADD_WIDGET_BUTTON_DRAG_ID}
  37. style={{originX: 0, originY: 0}}
  38. animate={
  39. transform
  40. ? {
  41. x: transform.x,
  42. y: transform.y,
  43. scaleX: transform?.scaleX && transform.scaleX <= 1 ? transform.scaleX : 1,
  44. scaleY: transform?.scaleY && transform.scaleY <= 1 ? transform.scaleY : 1,
  45. }
  46. : initialStyles
  47. }
  48. transition={{
  49. duration: 0.25,
  50. }}
  51. >
  52. {hasDDMExperimentalFeature(organization) ? (
  53. <InnerWrapper>
  54. <AddWidgetButton
  55. onAddWidget={onAddWidget}
  56. aria-label="Add Widget"
  57. data-test-id="widget-add"
  58. />
  59. </InnerWrapper>
  60. ) : (
  61. <InnerWrapper onClick={() => onAddWidget(DataSet.EVENTS)}>
  62. <AddButton
  63. data-test-id="widget-add"
  64. icon={<IconAdd size="lg" isCircled color="inactive" />}
  65. aria-label={t('Add widget')}
  66. />
  67. </InnerWrapper>
  68. )}
  69. </WidgetWrapper>
  70. );
  71. }
  72. const AddButton = styled(Button)`
  73. border: none;
  74. &,
  75. &:focus,
  76. &:active,
  77. &:hover {
  78. background: transparent;
  79. box-shadow: none;
  80. }
  81. `;
  82. export default AddWidget;
  83. export function AddWidgetButton({onAddWidget, ...buttonProps}: Props & ButtonProps) {
  84. const organization = useOrganization();
  85. const datasetChoices = new Map<string, string>();
  86. datasetChoices.set(DataSet.EVENTS, t('Errors and Transactions'));
  87. datasetChoices.set(DataSet.ISSUES, t('Issues (States, Assignment, Time, etc.)'));
  88. if (organization.features.includes('dashboards-rh-widget')) {
  89. datasetChoices.set(DataSet.RELEASES, t('Releases (Sessions, Crash rates)'));
  90. }
  91. if (hasDDMFeature(organization)) {
  92. datasetChoices.set(DataSet.METRICS, t('Custom Metrics'));
  93. }
  94. return (
  95. <CompactSelect
  96. options={[...datasetChoices.entries()].map(([value, label]) => ({
  97. label,
  98. value,
  99. }))}
  100. trigger={triggerProps => (
  101. <DropdownButton
  102. {...triggerProps}
  103. {...buttonProps}
  104. data-test-id="widget-add"
  105. size="sm"
  106. icon={<IconAdd isCircled />}
  107. >
  108. {t('Add Widget')}
  109. </DropdownButton>
  110. )}
  111. onChange={({value: dataset}) => {
  112. trackAnalytics('dashboards_views.widget_library.opened', {
  113. organization,
  114. });
  115. onAddWidget(dataset as DataSet);
  116. }}
  117. />
  118. );
  119. }
  120. const InnerWrapper = styled('div')<{onClick?: () => void}>`
  121. width: 100%;
  122. height: 110px;
  123. border: 2px dashed ${p => p.theme.border};
  124. border-radius: ${p => p.theme.borderRadius};
  125. display: flex;
  126. align-items: center;
  127. justify-content: center;
  128. cursor: ${p => (p.onClick ? 'pointer' : '')};
  129. `;