import {Fragment, useRef, useState} from 'react';
import styled from '@emotion/styled';
import {RequestOptions} from 'sentry/api';
import {QuickContextCommitRow} from 'sentry/components/discover/quickContextCommitRow';
import EventCause from 'sentry/components/events/eventCause';
import {CauseHeader, DataSection} from 'sentry/components/events/styles';
import FeatureBadge from 'sentry/components/featureBadge';
import AssignedTo from 'sentry/components/group/assignedTo';
import {Body, Hovercard} from 'sentry/components/hovercard';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {Panel} from 'sentry/components/panels';
import * as SidebarSection from 'sentry/components/sidebarSection';
import {IconCheckmark, IconInfo, IconMute, IconNot} from 'sentry/icons';
import {t} from 'sentry/locale';
import GroupStore from 'sentry/stores/groupStore';
import space from 'sentry/styles/space';
import {Group, Organization} from 'sentry/types';
import {EventData} from 'sentry/utils/discover/eventView';
import useApi from 'sentry/utils/useApi';
// Will extend this enum as we add contexts for more columns
export enum ContextType {
ISSUE = 'issue',
RELEASE = 'release',
}
const HOVER_DELAY: number = 400;
const DATA_FETCH_DELAY: number = 200;
function isIssueContext(contextType: ContextType): boolean {
return contextType === ContextType.ISSUE;
}
type RequestParams = {
path: string;
options?: RequestOptions;
};
// NOTE: Will extend when we add more type of contexts. Context is only relevant to issue and release columns for now.
function getRequestParams(
dataRow: EventData,
contextType: ContextType,
organization?: Organization
): RequestParams {
return isIssueContext(contextType)
? {
path: `/issues/${dataRow['issue.id']}/`,
options: {
method: 'GET',
query: {
collapse: 'release',
expand: 'inbox',
},
},
}
: {
path: `/organizations/${organization?.slug}/releases/${dataRow.release}/`,
};
}
type QuickContextProps = {
contextType: ContextType;
data: Group | null;
dataRow: EventData;
error: boolean;
loading: boolean;
};
export default function QuickContext({
loading,
error,
data,
contextType,
dataRow,
}: QuickContextProps) {
return (
{loading ? (
) : error ? (
{t('Failed to load context for column.')}
) : isIssueContext(contextType) && data ? (
) : (
{t('There is no context available.')}
)}
);
}
type IssueContextProps = {
data: Group;
eventID?: string;
};
function IssueContext(props: IssueContextProps) {
const statusTitle = t('Issue Status');
const {status} = props.data;
const renderStatus = () => (
{statusTitle}
{status === 'ignored' ? (
) : status === 'resolved' ? (
) : (
)}
{status}
);
const renderAssigneeSelector = () => (
);
const renderSuspectCommits = () =>
props.eventID && (
);
return (
{renderStatus()}
{renderAssigneeSelector()}
{renderSuspectCommits()}
);
}
type ContextProps = {
children: React.ReactNode;
contextType: ContextType;
dataRow: EventData;
organization?: Organization;
};
export function QuickContextHoverWrapper(props: ContextProps) {
const api = useApi();
const [ishovering, setisHovering] = useState(false);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
const delayOpenTimeoutRef = useRef(undefined);
const handleHoverState = () => {
setisHovering(prevState => !prevState);
};
const fetchData = () => {
if (!data) {
const params = getRequestParams(
props.dataRow,
props.contextType,
props.organization
);
api
.requestPromise(params.path, params.options)
.then(response => {
setData(response);
if (isIssueContext(props.contextType)) {
GroupStore.add([response]);
}
})
.catch(() => {
setError(true);
})
.finally(() => {
setLoading(false);
});
}
};
const handleMouseEnter = () => {
handleHoverState();
delayOpenTimeoutRef.current = window.setTimeout(() => {
fetchData();
}, DATA_FETCH_DELAY);
};
const handleMouseLeave = () => {
handleHoverState();
window.clearTimeout(delayOpenTimeoutRef.current);
};
return (
{props.children}
}
>
e.preventDefault()}
/>
);
}
const ContextContainer = styled('div')`
display: flex;
flex-direction: column;
`;
const StyledHovercard = styled(Hovercard)`
${Body} {
padding: 0;
}
min-width: 300px;
`;
const StyledIconInfo = styled(IconInfo)<{ishovering: number}>`
color: ${p => (p.ishovering ? p.theme.gray300 : p.theme.gray200)};
`;
const HoverWrapper = styled('div')`
display: flex;
align-items: center;
gap: ${space(0.75)};
`;
const IssueContextContainer = styled(ContextContainer)`
${SidebarSection.Wrap}, ${Panel}, h6 {
margin: 0;
}
${Panel} {
border: none;
box-shadow: none;
}
${DataSection} {
padding: 0;
}
${CauseHeader}, ${SidebarSection.Title} {
margin-top: ${space(2)};
}
`;
const ContextTitle = styled('h6')`
color: ${p => p.theme.subText};
display: flex;
justify-content: space-between;
align-items: center;
font-size: ${p => p.theme.fontSizeMedium};
margin: 0;
`;
const ContextBody = styled('div')`
margin: ${space(1)} 0 0;
width: 100%;
text-align: left;
font-size: ${p => p.theme.fontSizeLarge};
display: flex;
align-items: center;
`;
const StatusText = styled('span')`
margin-left: ${space(1)};
text-transform: capitalize;
`;
const Wrapper = styled('div')`
background: ${p => p.theme.background};
border-radius: ${p => p.theme.borderRadius};
width: 300px;
padding: ${space(1.5)};
`;
const NoContextWrapper = styled('div')`
color: ${p => p.theme.subText};
height: 50px;
padding: ${space(1)};
font-size: ${p => p.theme.fontSizeMedium};
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
white-space: nowrap;
`;