|
@@ -1,18 +1,31 @@
|
|
-import {useMemo} from 'react';
|
|
|
|
|
|
+import {useMemo, useRef} from 'react';
|
|
import type {Theme} from '@emotion/react';
|
|
import type {Theme} from '@emotion/react';
|
|
import styled from '@emotion/styled';
|
|
import styled from '@emotion/styled';
|
|
|
|
|
|
import {Button} from 'sentry/components/button';
|
|
import {Button} from 'sentry/components/button';
|
|
-import {DropdownMenu, type MenuItemProps} from 'sentry/components/dropdownMenu';
|
|
|
|
|
|
+import type {MenuItemProps} from 'sentry/components/dropdownMenu';
|
|
|
|
+import {DropdownMenu} from 'sentry/components/dropdownMenu';
|
|
|
|
+import {DropdownMenuFooter} from 'sentry/components/dropdownMenu/footer';
|
|
|
|
+import useFeedbackWidget from 'sentry/components/feedback/widget/useFeedbackWidget';
|
|
|
|
+import Placeholder from 'sentry/components/placeholder';
|
|
import Tag from 'sentry/components/tag';
|
|
import Tag from 'sentry/components/tag';
|
|
import {IconChevron} from 'sentry/icons';
|
|
import {IconChevron} from 'sentry/icons';
|
|
-import {t} from 'sentry/locale';
|
|
|
|
|
|
+import {t, tct} from 'sentry/locale';
|
|
import {space} from 'sentry/styles/space';
|
|
import {space} from 'sentry/styles/space';
|
|
-import {PriorityLevel} from 'sentry/types';
|
|
|
|
|
|
+import {
|
|
|
|
+ type Activity,
|
|
|
|
+ type AvatarUser,
|
|
|
|
+ GroupActivityType,
|
|
|
|
+ PriorityLevel,
|
|
|
|
+} from 'sentry/types';
|
|
|
|
+import {defined} from 'sentry/utils';
|
|
|
|
+import {useApiQuery} from 'sentry/utils/queryClient';
|
|
|
|
|
|
type GroupPriorityDropdownProps = {
|
|
type GroupPriorityDropdownProps = {
|
|
|
|
+ groupId: string;
|
|
onChange: (value: PriorityLevel) => void;
|
|
onChange: (value: PriorityLevel) => void;
|
|
value: PriorityLevel;
|
|
value: PriorityLevel;
|
|
|
|
+ lastEditedBy?: 'system' | AvatarUser;
|
|
};
|
|
};
|
|
|
|
|
|
type GroupPriorityBadgeProps = {
|
|
type GroupPriorityBadgeProps = {
|
|
@@ -40,6 +53,33 @@ function getTagTypeForPriority(priority: string): keyof Theme['tag'] {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+function useLastEditedBy({
|
|
|
|
+ groupId,
|
|
|
|
+ lastEditedBy: incomingLastEditedBy,
|
|
|
|
+}: Pick<GroupPriorityDropdownProps, 'groupId' | 'lastEditedBy'>) {
|
|
|
|
+ const {data} = useApiQuery<{activity: Activity[]}>([`/issues/${groupId}/activities/`], {
|
|
|
|
+ enabled: !defined(incomingLastEditedBy),
|
|
|
|
+ staleTime: 0,
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const lastEditedBy = useMemo(() => {
|
|
|
|
+ if (incomingLastEditedBy) {
|
|
|
|
+ return incomingLastEditedBy;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!data) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return (
|
|
|
|
+ data?.activity?.find(activity => activity.type === GroupActivityType.SET_PRIORITY)
|
|
|
|
+ ?.user ?? 'system'
|
|
|
|
+ );
|
|
|
|
+ }, [data, incomingLastEditedBy]);
|
|
|
|
+
|
|
|
|
+ return lastEditedBy;
|
|
|
|
+}
|
|
|
|
+
|
|
export function GroupPriorityBadge({priority, children}: GroupPriorityBadgeProps) {
|
|
export function GroupPriorityBadge({priority, children}: GroupPriorityBadgeProps) {
|
|
return (
|
|
return (
|
|
<StyledTag type={getTagTypeForPriority(priority)}>
|
|
<StyledTag type={getTagTypeForPriority(priority)}>
|
|
@@ -49,7 +89,49 @@ export function GroupPriorityBadge({priority, children}: GroupPriorityBadgeProps
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
-export function GroupPriorityDropdown({value, onChange}: GroupPriorityDropdownProps) {
|
|
|
|
|
|
+function PriorityChangeActor({
|
|
|
|
+ groupId,
|
|
|
|
+ lastEditedBy,
|
|
|
|
+}: Pick<GroupPriorityDropdownProps, 'groupId' | 'lastEditedBy'>) {
|
|
|
|
+ const resolvedLastEditedBy = useLastEditedBy({groupId, lastEditedBy});
|
|
|
|
+
|
|
|
|
+ if (!resolvedLastEditedBy) {
|
|
|
|
+ return <InlinePlaceholder height="1em" width="60px" />;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (resolvedLastEditedBy === 'system') {
|
|
|
|
+ return <span>Sentry</span>;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return <span>{resolvedLastEditedBy.name}</span>;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function GroupPriorityFeedback() {
|
|
|
|
+ const buttonRef = useRef<HTMLButtonElement>(null);
|
|
|
|
+ const feedback = useFeedbackWidget({buttonRef});
|
|
|
|
+
|
|
|
|
+ if (!feedback) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return (
|
|
|
|
+ <StyledButton
|
|
|
|
+ ref={buttonRef}
|
|
|
|
+ size="zero"
|
|
|
|
+ borderless
|
|
|
|
+ onClick={e => e.stopPropagation()}
|
|
|
|
+ >
|
|
|
|
+ {t('Give Feedback')}
|
|
|
|
+ </StyledButton>
|
|
|
|
+ );
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export function GroupPriorityDropdown({
|
|
|
|
+ groupId,
|
|
|
|
+ value,
|
|
|
|
+ onChange,
|
|
|
|
+ lastEditedBy,
|
|
|
|
+}: GroupPriorityDropdownProps) {
|
|
const options: MenuItemProps[] = useMemo(() => {
|
|
const options: MenuItemProps[] = useMemo(() => {
|
|
return PRIORITY_OPTIONS.map(priority => ({
|
|
return PRIORITY_OPTIONS.map(priority => ({
|
|
textValue: PRIORITY_KEY_TO_LABEL[priority],
|
|
textValue: PRIORITY_KEY_TO_LABEL[priority],
|
|
@@ -62,8 +144,13 @@ export function GroupPriorityDropdown({value, onChange}: GroupPriorityDropdownPr
|
|
return (
|
|
return (
|
|
<DropdownMenu
|
|
<DropdownMenu
|
|
size="sm"
|
|
size="sm"
|
|
- menuTitle={t('Set Priority To...')}
|
|
|
|
- minMenuWidth={160}
|
|
|
|
|
|
+ menuTitle={
|
|
|
|
+ <MenuTitleContainer>
|
|
|
|
+ <div>{t('Set Priority')}</div>
|
|
|
|
+ <GroupPriorityFeedback />
|
|
|
|
+ </MenuTitleContainer>
|
|
|
|
+ }
|
|
|
|
+ minMenuWidth={210}
|
|
trigger={triggerProps => (
|
|
trigger={triggerProps => (
|
|
<DropdownButton
|
|
<DropdownButton
|
|
{...triggerProps}
|
|
{...triggerProps}
|
|
@@ -76,6 +163,16 @@ export function GroupPriorityDropdown({value, onChange}: GroupPriorityDropdownPr
|
|
</DropdownButton>
|
|
</DropdownButton>
|
|
)}
|
|
)}
|
|
items={options}
|
|
items={options}
|
|
|
|
+ menuFooter={
|
|
|
|
+ <DropdownMenuFooter>
|
|
|
|
+ <div>
|
|
|
|
+ {tct('Last edited by [name]', {
|
|
|
|
+ name: <PriorityChangeActor groupId={groupId} lastEditedBy={lastEditedBy} />,
|
|
|
|
+ })}
|
|
|
|
+ </div>
|
|
|
|
+ </DropdownMenuFooter>
|
|
|
|
+ }
|
|
|
|
+ position="bottom-end"
|
|
/>
|
|
/>
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -95,3 +192,26 @@ const StyledTag = styled(Tag)`
|
|
gap: ${space(0.5)};
|
|
gap: ${space(0.5)};
|
|
}
|
|
}
|
|
`;
|
|
`;
|
|
|
|
+
|
|
|
|
+const InlinePlaceholder = styled(Placeholder)`
|
|
|
|
+ display: inline-block;
|
|
|
|
+ vertical-align: middle;
|
|
|
|
+`;
|
|
|
|
+
|
|
|
|
+const MenuTitleContainer = styled('div')`
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: flex-end;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+`;
|
|
|
|
+
|
|
|
|
+const StyledButton = styled(Button)`
|
|
|
|
+ font-size: ${p => p.theme.fontSizeSmall};
|
|
|
|
+ color: ${p => p.theme.subText};
|
|
|
|
+ font-weight: normal;
|
|
|
|
+ padding: 0;
|
|
|
|
+ border: none;
|
|
|
|
+
|
|
|
|
+ &:hover {
|
|
|
|
+ color: ${p => p.theme.subText};
|
|
|
|
+ }
|
|
|
|
+`;
|