// XXX(epurkhiser): When we switch to the new React JSX runtime we will no
// longer need this import and can drop babel-preset-css-prop for babel-preset.
///
import {FocusTrap} from 'focus-trap';
import u2f from 'u2f-api';
import exportGlobals from 'app/bootstrap/exportGlobals';
import Alert from 'app/components/alert';
import {getInterval} from 'app/components/charts/utils';
import {SymbolicatorStatus} from 'app/components/events/interfaces/types';
import {API_ACCESS_SCOPES, DEFAULT_RELATIVE_PERIODS} from 'app/constants';
import {PlatformKey} from 'app/data/platformCategories';
import {OrgExperiments, UserExperiments} from 'app/types/experiments';
import {
INSTALLED,
NOT_INSTALLED,
PENDING,
} from 'app/views/organizationIntegrations/constants';
import {Field} from 'app/views/settings/components/forms/type';
import {DynamicSamplingRules} from './dynamicSampling';
import {Event} from './event';
import {Mechanism, RawStacktrace, StacktraceType} from './stacktrace';
export enum SentryInitRenderReactComponent {
INDICATORS = 'Indicators',
SETUP_WIZARD = 'SetupWizard',
SYSTEM_ALERTS = 'SystemAlerts',
U2F_SIGN = 'U2fSign',
}
export type OnSentryInitConfiguration =
| {
name: 'passwordStrength';
input: string;
element: string;
}
| {
name: 'renderReact';
container: string;
component: SentryInitRenderReactComponent;
props?: Record;
}
| {
name: 'onReady';
onReady: (globals: typeof exportGlobals) => void;
};
declare global {
interface Window {
/**
* Assets public location
*/
__sentryGlobalStaticPrefix: string;
/**
* The config object provided by the backend.
*/
__initialData: Config;
/**
* Pipeline
*/
__pipelineInitialData: PipelineInitialData;
/**
* This allows our server-rendered templates to push configuration that should be
* run after we render our main application.
*
* An example of this is dynamically importing the `passwordStrength` module only
* on the organization login page.
*/
__onSentryInit:
| OnSentryInitConfiguration[]
| {
push: (config: OnSentryInitConfiguration) => void;
};
/**
* Sentrys version string
*/
__SENTRY__VERSION?: string;
/**
* The CSRF cookie ised on the backend
*/
csrfCookieName?: string;
/**
* Used to open tooltips for testing purposes.
*/
__openAllTooltips: () => void;
/**
* Used to close tooltips for testing purposes.
*/
__closeAllTooltips: () => void;
/**
* Primary entrypoint for rendering the sentry app. This is typically
* called in the django templates, or in the case of the EXPERIMENTAL_SPA,
* after config hydration.
*/
SentryRenderApp: () => void;
sentryEmbedCallback?: ((embed: any) => void) | null;
/**
* Set to true if adblock could be installed.
* See sentry/js/ads.js for how this global is disabled.
*/
adblockSuspected?: boolean;
// typing currently used for demo add on
// TODO: improve typing
SentryApp?: {
HookStore: any;
ConfigStore: any;
Modal: any;
modalFocusTrap?: {
current?: FocusTrap;
};
getModalPortal: () => HTMLElement;
};
}
}
export type PipelineInitialData = {
name: string;
props: Record;
};
export type IntegrationInstallationStatus =
| typeof INSTALLED
| typeof NOT_INSTALLED
| typeof PENDING;
export type SentryAppStatus = 'unpublished' | 'published' | 'internal';
export type ObjectStatus =
| 'active'
| 'disabled'
| 'pending_deletion'
| 'deletion_in_progress';
export type Avatar = {
avatarUuid: string | null;
avatarType: 'letter_avatar' | 'upload' | 'gravatar' | 'background';
};
export type Actor = {
type: 'user' | 'team';
id: string;
name: string;
email?: string;
};
/**
* Organization summaries are sent when you request a
* list of all organizations
*/
export type OrganizationSummary = {
status: {
// TODO(ts): Are these fields == `ObjectStatus`?
id: string;
name: string;
};
require2FA: boolean;
avatar: Avatar;
features: string[];
name: string;
dateCreated: string;
id: string;
isEarlyAdopter: boolean;
slug: string;
};
export type Relay = {
publicKey: string;
name: string;
created?: string;
lastModified?: string;
description?: string;
};
export type RelayActivity = {
publicKey: string;
relayId: string;
version: string;
firstSeen: string;
lastSeen: string;
};
export type RelaysByPublickey = {
[publicKey: string]: {
name: string;
activities: Array;
description?: string;
created?: string;
};
};
/**
* Detailed organization (e.g. when requesting details for a single org)
*
* Lightweight in this case means it does not contain `projects` or `teams`
*/
export type LightWeightOrganization = OrganizationSummary & {
relayPiiConfig: string;
scrubIPAddresses: boolean;
attachmentsRole: string;
debugFilesRole: string;
eventsMemberAdmin: boolean;
alertsMemberWrite: boolean;
sensitiveFields: string[];
openMembership: boolean;
quota: {
maxRateInterval: number | null;
projectLimit: number | null;
accountLimit: number | null;
maxRate: number | null;
};
defaultRole: string;
experiments: Partial;
allowJoinRequests: boolean;
scrapeJavaScript: boolean;
isDefault: boolean;
pendingAccessRequests: number;
availableRoles: {id: string; name: string}[];
enhancedPrivacy: boolean;
safeFields: string[];
storeCrashReports: number;
access: Scope[];
allowSharedIssues: boolean;
dataScrubberDefaults: boolean;
dataScrubber: boolean;
apdexThreshold: number;
onboardingTasks: OnboardingTaskStatus[];
trustedRelays: Relay[];
role?: string;
};
/**
* Full organization details
*/
export type Organization = LightWeightOrganization & {
projects: Project[];
teams: Team[];
};
/**
* Minimal organization shape used on shared issue views.
*/
export type SharedViewOrganization = {
slug: string;
id?: string;
features?: Array;
};
// Minimal project representation for use with avatars.
export type AvatarProject = {
slug: string;
platform?: PlatformKey;
id?: string | number;
};
/**
* Simple timeseries data used in groups, projects and release health.
*/
export type TimeseriesValue = [timestamp: number, value: number];
export type Project = {
id: string;
dateCreated: string;
isMember: boolean;
teams: Team[];
features: string[];
organization: Organization;
isBookmarked: boolean;
isInternal: boolean;
hasUserReports?: boolean;
hasAccess: boolean;
firstEvent: 'string' | null;
firstTransactionEvent: boolean;
subjectTemplate: string;
digestsMaxDelay: number;
digestsMinDelay: number;
environments: string[];
// XXX: These are part of the DetailedProject serializer
dynamicSampling: {
next_id: number;
rules: DynamicSamplingRules;
} | null;
plugins: Plugin[];
processingIssues: number;
relayPiiConfig: string;
groupingConfig: string;
latestDeploys?: Record> | null;
builtinSymbolSources?: string[];
symbolSources?: string;
stats?: TimeseriesValue[];
transactionStats?: TimeseriesValue[];
latestRelease?: Release;
options?: Record;
sessionStats?: {
currentCrashFreeRate: number | null;
previousCrashFreeRate: number | null;
hasHealthData: boolean;
};
} & AvatarProject;
export type MinimalProject = Pick;
// Response from project_keys endpoints.
export type ProjectKey = {
id: string;
name: string;
label: string;
public: string;
secret: string;
projectId: string;
isActive: boolean;
rateLimit: {
window: string;
count: number;
} | null;
dsn: {
secret: string;
public: string;
csp: string;
security: string;
minidump: string;
unreal: string;
cdn: string;
};
browserSdkVersion: string;
browserSdk: {
choices: [key: string, value: string][];
};
dateCreated: string;
};
export type Health = {
totalUsers: number;
totalUsers24h: number | null;
totalProjectUsers24h: number | null;
totalSessions: number;
totalSessions24h: number | null;
totalProjectSessions24h: number | null;
crashFreeUsers: number | null;
crashFreeSessions: number | null;
stats: HealthGraphData;
sessionsCrashed: number;
sessionsErrored: number;
adoption: number | null;
sessionsAdoption: number | null;
hasHealthData: boolean;
durationP50: number | null;
durationP90: number | null;
};
export type HealthGraphData = Record;
export type Team = {
id: string;
name: string;
slug: string;
isMember: boolean;
hasAccess: boolean;
isPending: boolean;
memberCount: number;
avatar: Avatar;
externalTeams: ExternalTeam[];
};
export type TeamWithProjects = Team & {projects: Project[]};
export type TreeLabelPart =
| string
| {
function?: string;
package?: string;
type?: string;
classbase?: string;
datapath?: (string | number)[];
is_sentinel?: boolean;
is_prefix?: boolean;
};
// This type is incomplete
export type EventMetadata = {
value?: string;
message?: string;
directive?: string;
type?: string;
title?: string;
uri?: string;
filename?: string;
origin?: string;
function?: string;
stripped_crash?: boolean;
current_tree_label?: TreeLabelPart[];
finest_tree_label?: TreeLabelPart[];
current_level?: number;
};
export type EventAttachment = {
id: string;
dateCreated: string;
headers: Object;
mimetype: string;
name: string;
sha1: string;
size: number;
type: string;
event_id: string;
};
export type EntryData = Record>;
type EnableIntegrationSuggestion = {
type: 'enableIntegration';
integrationName: string;
enables: Array;
integrationUrl?: string | null;
};
export type UpdateSdkSuggestion = {
type: 'updateSdk';
sdkName: string;
newSdkVersion: string;
enables: Array;
sdkUrl?: string | null;
};
type ChangeSdkSuggestion = {
type: 'changeSdk';
newSdkName: string;
enables: Array;
sdkUrl?: string | null;
};
export type SDKUpdatesSuggestion =
| EnableIntegrationSuggestion
| UpdateSdkSuggestion
| ChangeSdkSuggestion;
export type ProjectSdkUpdates = {
projectId: string;
sdkName: string;
sdkVersion: string;
suggestions: SDKUpdatesSuggestion[];
};
export type EventsStatsData = [number, {count: number}[]][];
// API response format for a single series
export type EventsStats = {
data: EventsStatsData;
totals?: {count: number};
order?: number;
start?: number;
end?: number;
};
// API response format for multiple series
export type MultiSeriesEventsStats = {
[seriesName: string]: EventsStats;
};
/**
* Avatars are a more primitive version of User.
*/
export type AvatarUser = {
id: string;
name: string;
username: string;
email: string;
ip_address: string;
avatarUrl?: string;
avatar?: Avatar;
// Compatibility shim with EventUser serializer
ipAddress?: string;
options?: {
avatarType: Avatar['avatarType'];
};
lastSeen?: string;
};
/**
* This is an authenticator that a user is enrolled in
*/
type UserEnrolledAuthenticator = {
dateUsed: EnrolledAuthenticator['lastUsedAt'];
dateCreated: EnrolledAuthenticator['createdAt'];
type: Authenticator['id'];
id: EnrolledAuthenticator['authId'];
};
export type User = Omit & {
lastLogin: string;
isSuperuser: boolean;
isAuthenticated: boolean;
emails: {
is_verified: boolean;
id: string;
email: string;
}[];
isManaged: boolean;
lastActive: string;
isStaff: boolean;
identities: any[];
isActive: boolean;
has2fa: boolean;
canReset2fa: boolean;
authenticators: UserEnrolledAuthenticator[];
dateJoined: string;
options: {
theme: 'system' | 'light' | 'dark';
timezone: string;
stacktraceOrder: number;
language: string;
clock24Hours: boolean;
avatarType: Avatar['avatarType'];
};
flags: {newsletter_consent_prompt: boolean};
hasPasswordAuth: boolean;
permissions: Set;
experiments: Partial;
};
// XXX(epurkhiser): we should understand how this is diff from User['emails]
// above
export type UserEmail = {
email: string;
isPrimary: boolean;
isVerified: boolean;
};
export type CommitAuthor = {
email?: string;
name?: string;
};
export type Environment = {
id: string;
displayName: string;
name: string;
// XXX: Provided by the backend but unused due to `getUrlRoutingName()`
// urlRoutingName: string;
};
export type RecentSearch = {
id: string;
organizationId: string;
type: SavedSearchType;
query: string;
lastSeen: string;
dateCreated: string;
};
// XXX: Deprecated Sentry 9 attributes are not included here.
export type SavedSearch = {
id: string;
type: SavedSearchType;
name: string;
query: string;
sort: string;
isGlobal: boolean;
isPinned: boolean;
isOrgCustom: boolean;
dateCreated: string;
};
export enum SavedSearchType {
ISSUE = 0,
EVENT = 1,
}
export type PluginNoProject = {
id: string;
name: string;
slug: string;
shortName: string;
type: string;
canDisable: boolean;
isTestable: boolean;
hasConfiguration: boolean;
metadata: any; // TODO(ts)
contexts: any[]; // TODO(ts)
status: string;
assets: Array<{url: string}>;
doc: string;
features: string[];
featureDescriptions: IntegrationFeature[];
isHidden: boolean;
version?: string;
author?: {name: string; url: string};
description?: string;
resourceLinks?: Array<{title: string; url: string}>;
altIsSentryApp?: boolean;
deprecationDate?: string;
firstPartyAlternative?: string;
};
export type Plugin = PluginNoProject & {
enabled: boolean;
};
export type PluginProjectItem = {
projectId: string;
projectSlug: string;
projectName: string;
projectPlatform: PlatformKey;
enabled: boolean;
configured: boolean;
};
export type PluginWithProjectList = PluginNoProject & {
projectList: PluginProjectItem[];
};
export type AppOrProviderOrPlugin =
| SentryApp
| IntegrationProvider
| PluginWithProjectList
| DocumentIntegration;
export type IntegrationType = 'document' | 'plugin' | 'first_party' | 'sentry_app';
export type DocumentIntegration = {
slug: string;
name: string;
author: string;
docUrl: string;
description: string;
features: IntegrationFeature[];
resourceLinks: Array<{title: string; url: string}>;
};
export type DateString = Date | string | null;
export type RelativePeriod = keyof typeof DEFAULT_RELATIVE_PERIODS;
export type IntervalPeriod = ReturnType;
export type GlobalSelection = {
// Project Ids currently selected
projects: number[];
environments: string[];
datetime: {
start: DateString;
end: DateString;
period: RelativePeriod | string;
utc: boolean | null;
};
};
export type AuthenticatorDevice = {
key_handle: string;
authId: string;
name: string;
timestamp?: string;
};
export type Authenticator = {
/**
* String used to display on button for user as CTA to enroll
*/
enrollButton: string;
/**
* Display name for the authenticator
*/
name: string;
/**
* Allows multiple enrollments to authenticator
*/
allowMultiEnrollment: boolean;
/**
* Allows authenticator's secret to be rotated without disabling
*/
allowRotationInPlace: boolean;
/**
* String to display on button for user to remove authenticator
*/
removeButton: string | null;
canValidateOtp: boolean;
/**
* Is user enrolled to this authenticator
*/
isEnrolled: boolean;
/**
* String to display on button for additional information about authenticator
*/
configureButton: string;
/**
* Is this used as a backup interface?
*/
isBackupInterface: boolean;
/**
* Description of the authenticator
*/
description: string;
rotationWarning: string | null;
status: string;
createdAt: string | null;
lastUsedAt: string | null;
codes: string[];
devices: AuthenticatorDevice[];
phone?: string;
secret?: string;
/**
* The form configuration for the authenticator is present during enrollment
*/
form?: Field[];
} & Partial &
(
| {
id: 'sms';
}
| {
id: 'totp';
qrcode: string;
}
| {
id: 'u2f';
challenge: ChallengeData;
}
);
export type ChallengeData = {
authenticateRequests: u2f.SignRequest;
registerRequests: u2f.RegisterRequest;
};
export type EnrolledAuthenticator = {
lastUsedAt: string | null;
createdAt: string;
authId: string;
};
export interface Config {
theme: 'light' | 'dark';
languageCode: string;
csrfCookieName: string;
features: Set;
singleOrganization: boolean;
urlPrefix: string;
needsUpgrade: boolean;
supportEmail: string;
user: User;
invitesEnabled: boolean;
privacyUrl: string | null;
isOnPremise: boolean;
lastOrganization: string | null;
gravatarBaseUrl: string;
/**
* This comes from django (django.contrib.messages)
*/
messages: {message: string; level: string}[];
dsn: string;
userIdentity: {ip_address: string; email: string; id: string; isStaff: boolean};
termsUrl: string | null;
isAuthenticated: boolean;
version: {
current: string;
build: string;
upgradeAvailable: boolean;
latest: string;
};
statuspage?: {
id: string;
api_host: string;
};
sentryConfig: {
dsn: string;
release: string;
whitelistUrls: string[];
};
distPrefix: string;
apmSampling: number;
dsn_requests: string;
demoMode: boolean;
}
// https://github.com/getsentry/relay/blob/master/relay-common/src/constants.rs
// Note: the value of the enum on the frontend is plural,
// but the value of the enum on the backend is singular
export enum DataCategory {
DEFAULT = 'default',
ERRORS = 'errors',
TRANSACTIONS = 'transactions',
ATTACHMENTS = 'attachments',
}
export type EventType = 'error' | 'transaction' | 'attachment';
export const DataCategoryName = {
[DataCategory.ERRORS]: 'Errors',
[DataCategory.TRANSACTIONS]: 'Transactions',
[DataCategory.ATTACHMENTS]: 'Attachments',
};
export enum EventOrGroupType {
ERROR = 'error',
CSP = 'csp',
HPKP = 'hpkp',
EXPECTCT = 'expectct',
EXPECTSTAPLE = 'expectstaple',
DEFAULT = 'default',
TRANSACTION = 'transaction',
}
export type InboxReasonDetails = {
until?: string | null;
count?: number | null;
window?: number | null;
user_count?: number | null;
user_window?: number | null;
};
export type InboxDetails = {
reason_details: InboxReasonDetails;
date_added?: string;
reason?: number;
};
export type SuggestedOwnerReason = 'suspectCommit' | 'ownershipRule';
// Received from the backend to denote suggested owners of an issue
export type SuggestedOwner = {
type: SuggestedOwnerReason;
owner: string;
date_added: string;
};
export enum GroupActivityType {
NOTE = 'note',
SET_RESOLVED = 'set_resolved',
SET_RESOLVED_BY_AGE = 'set_resolved_by_age',
SET_RESOLVED_IN_RELEASE = 'set_resolved_in_release',
SET_RESOLVED_IN_COMMIT = 'set_resolved_in_commit',
SET_RESOLVED_IN_PULL_REQUEST = 'set_resolved_in_pull_request',
SET_UNRESOLVED = 'set_unresolved',
SET_IGNORED = 'set_ignored',
SET_PUBLIC = 'set_public',
SET_PRIVATE = 'set_private',
SET_REGRESSION = 'set_regression',
CREATE_ISSUE = 'create_issue',
UNMERGE_SOURCE = 'unmerge_source',
UNMERGE_DESTINATION = 'unmerge_destination',
FIRST_SEEN = 'first_seen',
ASSIGNED = 'assigned',
UNASSIGNED = 'unassigned',
MERGE = 'merge',
REPROCESS = 'reprocess',
MARK_REVIEWED = 'mark_reviewed',
}
type GroupActivityBase = {
dateCreated: string;
id: string;
project: Project;
user?: null | User;
assignee?: string;
issue?: Group;
};
type GroupActivityNote = GroupActivityBase & {
type: GroupActivityType.NOTE;
data: {
text: string;
};
};
type GroupActivitySetResolved = GroupActivityBase & {
type: GroupActivityType.SET_RESOLVED;
data: Record;
};
type GroupActivitySetUnresolved = GroupActivityBase & {
type: GroupActivityType.SET_UNRESOLVED;
data: Record;
};
type GroupActivitySetPublic = GroupActivityBase & {
type: GroupActivityType.SET_PUBLIC;
data: Record;
};
type GroupActivitySetPrivate = GroupActivityBase & {
type: GroupActivityType.SET_PRIVATE;
data: Record;
};
type GroupActivitySetByAge = GroupActivityBase & {
type: GroupActivityType.SET_RESOLVED_BY_AGE;
data: Record;
};
type GroupActivityUnassigned = GroupActivityBase & {
type: GroupActivityType.UNASSIGNED;
data: Record;
};
type GroupActivityFirstSeen = GroupActivityBase & {
type: GroupActivityType.FIRST_SEEN;
data: Record;
};
type GroupActivityMarkReviewed = GroupActivityBase & {
type: GroupActivityType.MARK_REVIEWED;
data: Record;
};
type GroupActivityRegression = GroupActivityBase & {
type: GroupActivityType.SET_REGRESSION;
data: {
version?: string;
};
};
export type GroupActivitySetByResolvedInRelease = GroupActivityBase & {
type: GroupActivityType.SET_RESOLVED_IN_RELEASE;
data: {
version?: string;
current_release_version?: string;
};
};
type GroupActivitySetByResolvedInCommit = GroupActivityBase & {
type: GroupActivityType.SET_RESOLVED_IN_COMMIT;
data: {
commit: Commit;
};
};
type GroupActivitySetByResolvedInPullRequest = GroupActivityBase & {
type: GroupActivityType.SET_RESOLVED_IN_PULL_REQUEST;
data: {
pullRequest: PullRequest;
};
};
export type GroupActivitySetIgnored = GroupActivityBase & {
type: GroupActivityType.SET_IGNORED;
data: {
ignoreDuration?: number;
ignoreUntil?: string;
ignoreUserCount?: number;
ignoreUserWindow?: number;
ignoreWindow?: number;
ignoreCount?: number;
};
};
export type GroupActivityReprocess = GroupActivityBase & {
type: GroupActivityType.REPROCESS;
data: {
eventCount: number;
newGroupId: number;
oldGroupId: number;
};
};
type GroupActivityUnmergeDestination = GroupActivityBase & {
type: GroupActivityType.UNMERGE_DESTINATION;
data: {
fingerprints: Array;
source?: {
id: string;
shortId: string;
};
};
};
type GroupActivityUnmergeSource = GroupActivityBase & {
type: GroupActivityType.UNMERGE_SOURCE;
data: {
fingerprints: Array;
destination?: {
id: string;
shortId: string;
};
};
};
type GroupActivityMerge = GroupActivityBase & {
type: GroupActivityType.MERGE;
data: {
issues: Array;
};
};
export type GroupActivityAssigned = GroupActivityBase & {
type: GroupActivityType.ASSIGNED;
data: {
assignee: string;
assigneeType: string;
user: Team | User;
};
};
export type GroupActivityCreateIssue = GroupActivityBase & {
type: GroupActivityType.CREATE_ISSUE;
data: {
provider: string;
location: string;
title: string;
};
};
export type GroupActivity =
| GroupActivityNote
| GroupActivitySetResolved
| GroupActivitySetUnresolved
| GroupActivitySetIgnored
| GroupActivitySetByAge
| GroupActivitySetByResolvedInRelease
| GroupActivitySetByResolvedInCommit
| GroupActivitySetByResolvedInPullRequest
| GroupActivityFirstSeen
| GroupActivityMerge
| GroupActivityReprocess
| GroupActivityUnassigned
| GroupActivityMarkReviewed
| GroupActivityUnmergeDestination
| GroupActivitySetPublic
| GroupActivitySetPrivate
| GroupActivityRegression
| GroupActivityUnmergeSource
| GroupActivityAssigned
| GroupActivityCreateIssue;
export type Activity = GroupActivity;
type GroupFiltered = {
count: string;
stats: Record;
lastSeen: string;
firstSeen: string;
userCount: number;
};
export type GroupStats = GroupFiltered & {
lifetime?: GroupFiltered;
filtered: GroupFiltered | null;
sessionCount?: string | null;
id: string;
};
export type BaseGroupStatusReprocessing = {
status: 'reprocessing';
statusDetails: {
pendingEvents: number;
info: {
dateCreated: string;
totalEvents: number;
};
};
};
type BaseGroupStatusResolution = {
status: ResolutionStatus;
statusDetails: ResolutionStatusDetails;
};
export type GroupRelease = {
firstRelease: Release;
lastRelease: Release;
};
// TODO(ts): incomplete
export type BaseGroup = {
id: string;
latestEvent: Event;
activity: GroupActivity[];
annotations: string[];
assignedTo: Actor;
culprit: string;
firstSeen: string;
hasSeen: boolean;
isBookmarked: boolean;
isUnhandled: boolean;
isPublic: boolean;
isSubscribed: boolean;
lastSeen: string;
level: Level;
logger: string;
metadata: EventMetadata;
numComments: number;
participants: User[];
permalink: string;
platform: PlatformKey;
pluginActions: any[]; // TODO(ts)
pluginContexts: any[]; // TODO(ts)
pluginIssues: any[]; // TODO(ts)
project: Project;
seenBy: User[];
shareId: string;
shortId: string;
tags: Pick[];
title: string;
type: EventOrGroupType;
userReportCount: number;
subscriptionDetails: {disabled?: boolean; reason?: string} | null;
status: string;
inbox?: InboxDetails | null | false;
owners?: SuggestedOwner[] | null;
} & GroupRelease;
export type GroupReprocessing = BaseGroup & GroupStats & BaseGroupStatusReprocessing;
export type GroupResolution = BaseGroup & GroupStats & BaseGroupStatusResolution;
export type Group = GroupResolution | GroupReprocessing;
export type GroupCollapseRelease = Omit &
Partial;
export type GroupTombstone = {
id: string;
title: string;
culprit: string;
level: Level;
actor: AvatarUser;
metadata: EventMetadata;
};
export type ProcessingIssueItem = {
id: string;
type: string;
checksum: string;
numEvents: number;
data: {
// TODO(ts) This type is likely incomplete, but this is what
// project processing issues settings uses.
_scope: string;
image_arch: string;
image_uuid: string;
image_path: string;
};
lastSeen: string;
};
export type ProcessingIssue = {
project: string;
numIssues: number;
signedLink: string;
lastSeen: string;
hasMoreResolveableIssues: boolean;
hasIssues: boolean;
issuesProcessing: number;
resolveableIssues: number;
issues?: ProcessingIssueItem[];
};
/**
* Returned from /organizations/org/users/
*/
export type Member = {
dateCreated: string;
email: string;
expired: boolean;
flags: {
'sso:linked': boolean;
'sso:invalid': boolean;
'member-limit:restricted': boolean;
};
id: string;
inviteStatus: 'approved' | 'requested_to_be_invited' | 'requested_to_join';
invite_link: string | null;
inviterName: string | null;
isOnlyOwner: boolean;
name: string;
pending: boolean | undefined;
projects: string[];
role: string;
roleName: string;
roles: MemberRole[]; // TODO(ts): This is not present from API call
teams: string[];
user: User;
};
export type AccessRequest = {
id: string;
team: Team;
member: Member;
requester?: Partial<{
name: string;
username: string;
email: string;
}>;
};
export type Repository = {
dateCreated: string;
externalSlug: string;
id: string;
integrationId: string;
name: string;
provider: {id: string; name: string};
status: RepositoryStatus;
url: string;
};
export enum RepositoryStatus {
ACTIVE = 'active',
DISABLED = 'disabled',
HIDDEN = 'hidden',
PENDING_DELETION = 'pending_deletion',
DELETION_IN_PROGRESS = 'deletion_in_progress',
}
type BaseRepositoryProjectPathConfig = {
id: string;
projectId: string;
projectSlug: string;
repoId: string;
repoName: string;
stackRoot: string;
sourceRoot: string;
defaultBranch?: string;
};
export type RepositoryProjectPathConfig = BaseRepositoryProjectPathConfig & {
integrationId: string | null;
provider: BaseIntegrationProvider | null;
};
export type RepositoryProjectPathConfigWithIntegration =
BaseRepositoryProjectPathConfig & {
integrationId: string;
provider: BaseIntegrationProvider;
};
export type PullRequest = {
id: string;
title: string;
externalUrl: string;
repository: Repository;
};
type IntegrationDialog = {
actionText: string;
body: string;
};
type IntegrationAspects = {
alerts?: Array & {text: string}>;
disable_dialog?: IntegrationDialog;
removal_dialog?: IntegrationDialog;
externalInstall?: {
url: string;
buttonText: string;
noticeText: string;
};
configure_integration?: {
title: string;
};
};
type BaseIntegrationProvider = {
key: string;
slug: string;
name: string;
canAdd: boolean;
canDisable: boolean;
features: string[];
};
export type IntegrationProvider = BaseIntegrationProvider & {
setupDialog: {url: string; width: number; height: number};
metadata: {
description: string;
features: IntegrationFeature[];
author: string;
noun: string;
issue_url: string;
source_url: string;
aspects: IntegrationAspects;
};
};
export type IntegrationFeature = {
description: string;
featureGate: string;
};
export type WebhookEvent = 'issue' | 'error';
export type Scope = typeof API_ACCESS_SCOPES[number];
export type SentryAppSchemaIssueLink = {
type: 'issue-link';
create: {
uri: string;
required_fields: any[];
optional_fields?: any[];
};
link: {
uri: string;
required_fields: any[];
optional_fields?: any[];
};
};
export type SentryAppSchemaStacktraceLink = {
type: 'stacktrace-link';
uri: string;
url: string;
params?: Array;
};
export type SentryAppSchemaElement =
| SentryAppSchemaIssueLink
| SentryAppSchemaStacktraceLink;
export type SentryApp = {
status: SentryAppStatus;
scopes: Scope[];
isAlertable: boolean;
verifyInstall: boolean;
slug: string;
name: string;
uuid: string;
author: string;
events: WebhookEvent[];
schema: {
elements?: SentryAppSchemaElement[];
};
// possible null params
webhookUrl: string | null;
redirectUrl: string | null;
overview: string | null;
// optional params below
datePublished?: string;
clientId?: string;
clientSecret?: string;
owner?: {
id: number;
slug: string;
};
featureData: IntegrationFeature[];
};
export type Integration = {
id: string;
name: string;
icon: string;
domainName: string;
accountType: string;
status: ObjectStatus;
provider: BaseIntegrationProvider & {aspects: IntegrationAspects};
dynamicDisplayInformation?: {
configure_integration?: {
instructions: string[];
};
integration_detail?: {
uninstallationUrl?: string;
};
};
};
// we include the configOrganization when we need it
export type IntegrationWithConfig = Integration & {
configOrganization: Field[];
configData: object | null;
};
export type IntegrationExternalIssue = {
id: string;
key: string;
url: string;
title: string;
description: string;
displayName: string;
};
export type GroupIntegration = Integration & {
externalIssues: IntegrationExternalIssue[];
};
export type PlatformExternalIssue = {
id: string;
issueId: string;
serviceType: string;
displayName: string;
webUrl: string;
};
export type SentryAppInstallation = {
app: {
uuid: string;
slug: string;
};
organization: {
slug: string;
};
uuid: string;
status: 'installed' | 'pending';
code?: string;
};
export type SentryAppWebhookRequest = {
webhookUrl: string;
sentryAppSlug: string;
eventType: string;
date: string;
organization?: {
slug: string;
name: string;
};
responseCode: number;
errorUrl?: string;
};
export type ServiceHook = {
id: string;
events: string[];
dateCreated: string;
secret: string;
status: string;
url: string;
};
export type PermissionValue = 'no-access' | 'read' | 'write' | 'admin';
export type Permissions = {
Event: PermissionValue;
Member: PermissionValue;
Organization: PermissionValue;
Project: PermissionValue;
Release: PermissionValue;
Team: PermissionValue;
};
// See src/sentry/api/serializers/models/apitoken.py for the differences based on application
type BaseApiToken = {
id: string;
scopes: Scope[];
expiresAt: string;
dateCreated: string;
state: string;
};
// We include the token for API tokens used for internal apps
export type InternalAppApiToken = BaseApiToken & {
application: null;
token: string;
refreshToken: string;
};
export type ApiApplication = {
allowedOrigins: string[];
clientID: string;
clientSecret: string | null;
homepageUrl: string | null;
id: string;
name: string;
privacyUrl: string | null;
redirectUris: string[];
termsUrl: string | null;
};
export type UserReport = {
id: string;
eventID: string;
issue: Group;
name: string;
event: {eventID: string; id: string};
user: User;
dateCreated: string;
comments: string;
email: string;
};
export type Release = BaseRelease &
ReleaseData & {
projects: ReleaseProject[];
};
export type ReleaseWithHealth = BaseRelease &
ReleaseData & {
projects: Required[];
};
type ReleaseData = {
commitCount: number;
data: {};
lastDeploy?: Deploy;
deployCount: number;
lastEvent: string;
firstEvent: string;
lastCommit?: Commit;
authors: User[];
owner?: any; // TODO(ts)
newGroups: number;
versionInfo: VersionInfo;
fileCount: number | null;
currentProjectMeta: {
nextReleaseVersion: string | null;
prevReleaseVersion: string | null;
sessionsLowerBound: string | null;
sessionsUpperBound: string | null;
firstReleaseVersion: string | null;
lastReleaseVersion: string | null;
};
adoptionStages?: Record<
'string',
{
stage: string | null;
adopted: string | null;
unadopted: string | null;
}
>;
};
type BaseRelease = {
dateReleased: string;
url: string;
dateCreated: string;
version: string;
shortVersion: string;
ref: string;
status: ReleaseStatus;
};
export type CurrentRelease = {
environment: string;
firstSeen: string;
lastSeen: string;
release: Release;
stats: {
// 24h/30d is hardcoded in GroupReleaseWithStatsSerializer
'24h': TimeseriesValue[];
'30d': TimeseriesValue[];
};
};
export enum ReleaseStatus {
Active = 'open',
Archived = 'archived',
}
export type ReleaseProject = {
slug: string;
name: string;
id: number;
platform: PlatformKey;
platforms: PlatformKey[];
newGroups: number;
hasHealthData: boolean;
healthData?: Health;
};
export type ReleaseMeta = {
commitCount: number;
commitFilesChanged: number;
deployCount: number;
releaseFileCount: number;
version: string;
projects: ReleaseProject[];
versionInfo: VersionInfo;
released: string;
};
export type VersionInfo = {
buildHash: string | null;
description: string;
package: string | null;
version: {raw: string};
};
export type Deploy = {
id: string;
name: string;
url: string;
environment: string;
dateStarted: string;
dateFinished: string;
version: string;
};
export type Commit = {
id: string;
message: string | null;
dateCreated: string;
releases: BaseRelease[];
repository?: Repository;
author?: User;
};
export type Committer = {
author: User;
commits: Commit[];
};
export type CommitFile = {
id: string;
author: CommitAuthor;
commitMessage: string;
filename: string;
orgId: number;
repoName: string;
type: string;
};
export type MemberRole = {
id: string;
name: string;
desc: string;
allowed?: boolean;
};
export type SentryAppComponent = {
uuid: string;
type: 'issue-link' | 'alert-rule-action' | 'issue-media' | 'stacktrace-link';
schema: SentryAppSchemaStacktraceLink;
sentryApp: {
uuid: string;
slug:
| 'calixa'
| 'clickup'
| 'clubhouse'
| 'komodor'
| 'linear'
| 'rookout'
| 'spikesh'
| 'teamwork'
| 'zepel';
name: string;
};
};
export type SavedQueryVersions = 1 | 2;
export type NewQuery = {
id: string | undefined;
version: SavedQueryVersions;
name: string;
createdBy?: User;
// Query and Table
query?: string;
fields: Readonly;
widths?: Readonly;
orderby?: string;
expired?: boolean;
// GlobalSelectionHeader
projects: Readonly;
environment?: Readonly;
range?: string;
start?: string;
end?: string;
// Graph
yAxis?: string;
display?: string;
teams?: Readonly<('myteams' | number)[]>;
};
export type SavedQuery = NewQuery & {
id: string;
dateCreated: string;
dateUpdated: string;
};
export type SavedQueryState = {
savedQueries: SavedQuery[];
hasError: boolean;
isLoading: boolean;
};
/**
* The option format used by react-select based components
*/
export type SelectValue = {
label: string | number | React.ReactElement;
value: T;
disabled?: boolean;
tooltip?: string;
};
/**
* The 'other' option format used by checkboxes, radios and more.
*/
export type Choices = [
value: string | number,
label: string | number | React.ReactElement
][];
/**
* The issue config form fields we get are basically the form fields we use in
* the UI but with some extra information. Some fields marked optional in the
* form field are guaranteed to exist so we can mark them as required here
*/
export type IssueConfigField = Field & {
name: string;
default?: string | number;
choices?: Choices;
url?: string;
multiple?: boolean;
};
export type IntegrationIssueConfig = {
status: ObjectStatus;
name: string;
domainName: string;
linkIssueConfig?: IssueConfigField[];
createIssueConfig?: IssueConfigField[];
provider: IntegrationProvider;
icon: string[];
};
export enum OnboardingTaskKey {
FIRST_PROJECT = 'create_project',
FIRST_EVENT = 'send_first_event',
INVITE_MEMBER = 'invite_member',
SECOND_PLATFORM = 'setup_second_platform',
USER_CONTEXT = 'setup_user_context',
RELEASE_TRACKING = 'setup_release_tracking',
SOURCEMAPS = 'setup_sourcemaps',
USER_REPORTS = 'setup_user_reports',
ISSUE_TRACKER = 'setup_issue_tracker',
ALERT_RULE = 'setup_alert_rules',
FIRST_TRANSACTION = 'setup_transactions',
}
export type OnboardingSupplementComponentProps = {
task: OnboardingTask;
onCompleteTask: () => void;
};
export type OnboardingTaskDescriptor = {
task: OnboardingTaskKey;
title: string;
description: string;
/**
* Can this task be skipped?
*/
skippable: boolean;
/**
* A list of require task keys that must have been completed before these
* tasks may be completed.
*/
requisites: OnboardingTaskKey[];
/**
* Should the onboarding task currently be displayed
*/
display: boolean;
/**
* An extra component that may be rendered within the onboarding task item.
*/
SupplementComponent?: React.ComponentType;
} & (
| {
actionType: 'app' | 'external';
location: string;
}
| {
actionType: 'action';
action: () => void;
}
);
export type OnboardingTaskStatus = {
task: OnboardingTaskKey;
status: 'skipped' | 'pending' | 'complete';
user?: AvatarUser | null;
dateCompleted?: string;
completionSeen?: string;
data?: object;
};
export type OnboardingTask = OnboardingTaskStatus &
OnboardingTaskDescriptor & {
/**
* Onboarding tasks that are currently incomplete and must be completed
* before this task should be completed.
*/
requisiteTasks: OnboardingTask[];
};
export type Tag = {
name: string;
key: string;
values?: string[];
totalValues?: number;
predefined?: boolean;
isInput?: boolean;
/**
* How many values should be suggested in autocomplete.
* Overrides SmartSearchBar's `maxSearchItems` prop.
*/
maxSuggestedValues?: number;
};
export type TagCollection = Record;
export type TagValue = {
count: number;
name: string;
value: string;
lastSeen: string;
key: string;
firstSeen: string;
query?: string;
email?: string;
username?: string;
identifier?: string;
ipAddress?: string;
} & AvatarUser;
type Topvalue = {
count: number;
firstSeen: string;
key: string;
lastSeen: string;
name: string;
value: string;
// Might not actually exist.
query?: string;
};
export type TagWithTopValues = {
topValues: Array;
key: string;
name: string;
totalValues: number;
uniqueValues: number;
canDelete?: boolean;
};
export type Level = 'error' | 'fatal' | 'info' | 'warning' | 'sample';
export type Meta = {
chunks: Array;
len: number;
rem: Array;
err: Array;
};
export type MetaError = string | [string, any];
export type MetaRemark = Array;
export type ChunkType = {
text: string;
type: string;
rule_id: string | number;
remark?: string | number;
};
export enum ResolutionStatus {
RESOLVED = 'resolved',
UNRESOLVED = 'unresolved',
IGNORED = 'ignored',
}
export type ResolutionStatusDetails = {
actor?: AvatarUser;
autoResolved?: boolean;
ignoreCount?: number;
// Sent in requests. ignoreUntil is used in responses.
ignoreDuration?: number;
ignoreUntil?: string;
ignoreUserCount?: number;
ignoreUserWindow?: number;
ignoreWindow?: number;
inCommit?: Commit;
inRelease?: string;
inNextRelease?: boolean;
};
export type UpdateResolutionStatus = {
status: ResolutionStatus;
statusDetails?: ResolutionStatusDetails;
};
export type SubscriptionDetails = {disabled?: boolean; reason?: string};
export type Broadcast = {
id: string;
message: string;
title: string;
link: string;
cta: string;
isActive: boolean;
dateCreated: string;
dateExpires: string;
hasSeen: boolean;
};
export type SentryServiceIncident = {
id: string;
name: string;
updates?: string[];
url: string;
status: string;
};
export type SentryServiceStatus = {
indicator: 'major' | 'minor' | 'none';
incidents: SentryServiceIncident[];
url: string;
};
export type CrashFreeTimeBreakdown = {
date: string;
totalSessions: number;
crashFreeSessions: number | null;
crashFreeUsers: number | null;
totalUsers: number;
}[];
export type PlatformIntegration = {
id: string;
type: string;
language: string;
link: string | null;
name: string;
};
export type EventGroupComponent = {
contributes: boolean;
hint: string | null;
id: string;
name: string | null;
values: EventGroupComponent[] | string[];
};
export type EventGroupingConfig = {
base: string | null;
changelog: string;
delegates: string[];
hidden: boolean;
id: string;
latest: boolean;
risk: number;
strategies: string[];
};
type EventGroupVariantKey = 'custom-fingerprint' | 'app' | 'default' | 'system';
export enum EventGroupVariantType {
CUSTOM_FINGERPRINT = 'custom-fingerprint',
COMPONENT = 'component',
SALTED_COMPONENT = 'salted-component',
}
export type EventGroupVariant = {
description: string | null;
hash: string | null;
hashMismatch: boolean;
key: EventGroupVariantKey;
type: EventGroupVariantType;
values?: Array;
client_values?: Array;
matched_rule?: string;
component?: EventGroupComponent;
config?: EventGroupingConfig;
};
export type SourceMapsArchive = {
id: number;
type: 'release';
name: string;
date: string;
fileCount: number;
};
export type Artifact = {
dateCreated: string;
dist: string | null;
id: string;
name: string;
sha1: string;
size: number;
headers: {'Content-Type': string};
};
export type EventGroupInfo = Record;
// TODO(epurkhiser): objc and cocoa should almost definitely be moved into PlatformKey
export type PlatformType = PlatformKey | 'objc' | 'cocoa';
export type Frame = {
absPath: string | null;
colNo: number | null;
context: Array<[number, string]>;
errors: Array | null;
filename: string | null;
function: string | null;
inApp: boolean;
instructionAddr: string | null;
lineNo: number | null;
module: string | null;
package: string | null;
platform: PlatformType | null;
rawFunction: string | null;
symbol: string | null;
symbolAddr: string | null;
trust: any | null;
vars: Record | null;
symbolicatorStatus?: SymbolicatorStatus;
addrMode?: string;
origAbsPath?: string | null;
mapUrl?: string | null;
map?: string | null;
isSentinel?: boolean;
isPrefix?: boolean;
minGroupingLevel?: number;
};
export enum FrameBadge {
SENTINEL = 'sentinel',
PREFIX = 'prefix',
GROUPING = 'grouping',
}
/**
* Note used in Group Activity and Alerts for users to comment
*/
export type Note = {
/**
* Note contents (markdown allowed)
*/
text: string;
/**
* Array of [id, display string] tuples used for @-mentions
*/
mentions: [string, string][];
};
export type FilesByRepository = {
[repoName: string]: {
authors?: {[email: string]: CommitAuthor};
types?: Set;
};
};
export type ExceptionValue = {
type: string;
value: string;
threadId: number | null;
stacktrace: StacktraceType | null;
rawStacktrace: RawStacktrace;
mechanism: Mechanism | null;
module: string | null;
frames: Frame[] | null;
};
export type ExceptionType = {
excOmitted: any | null;
hasSystemFrames: boolean;
values?: Array;
};
/**
* Identity is used in Account Identities for SocialAuths
*/
export type Identity = {
id: string;
provider: IntegrationProvider;
providerLabel: string;
};
// taken from https://stackoverflow.com/questions/46634876/how-can-i-change-a-readonly-property-in-typescript
export type Writable = {-readonly [K in keyof T]: T[K]};
export type InternetProtocol = {
id: string;
ipAddress: string;
lastSeen: string;
firstSeen: string;
countryCode: string | null;
regionCode: string | null;
};
/**
* XXX(ts): This actually all comes from getsentry. We should definitely
* refactor this into a more proper 'hook' mechanism in the future
*/
export type AuthConfig = {
canRegister: boolean;
serverHostname: string;
hasNewsletter: boolean;
githubLoginLink: string;
vstsLoginLink: string;
googleLoginLink: string;
};
export type AuthProvider = {
key: string;
name: string;
requiredFeature: string;
disables2FA: boolean;
};
export type PromptActivity = {
snoozedTime?: number;
dismissedTime?: number;
};
export type ServerlessFunction = {
name: string;
runtime: string;
version: number;
outOfDate: boolean;
enabled: boolean;
};
/**
* Base type for series style API response
*/
export type SeriesApi = {
intervals: string[];
groups: {
by: Record;
totals: Record;
series: Record;
}[];
};
export type SessionApiResponse = SeriesApi & {
start: DateString;
end: DateString;
query: string;
intervals: string[];
groups: {
by: Record;
totals: Record;
series: Record;
}[];
};
export enum SessionField {
SESSIONS = 'sum(session)',
USERS = 'count_unique(user)',
DURATION = 'p50(session.duration)',
}
export enum SessionStatus {
HEALTHY = 'healthy',
ABNORMAL = 'abnormal',
ERRORED = 'errored',
CRASHED = 'crashed',
}
export enum ReleaseComparisonChartType {
CRASH_FREE_USERS = 'crashFreeUsers',
HEALTHY_USERS = 'healthyUsers',
ABNORMAL_USERS = 'abnormalUsers',
ERRORED_USERS = 'erroredUsers',
CRASHED_USERS = 'crashedUsers',
CRASH_FREE_SESSIONS = 'crashFreeSessions',
HEALTHY_SESSIONS = 'healthySessions',
ABNORMAL_SESSIONS = 'abnormalSessions',
ERRORED_SESSIONS = 'erroredSessions',
CRASHED_SESSIONS = 'crashedSessions',
SESSION_COUNT = 'sessionCount',
USER_COUNT = 'userCount',
ERROR_COUNT = 'errorCount',
TRANSACTION_COUNT = 'transactionCount',
FAILURE_RATE = 'failureRate',
SESSION_DURATION = 'sessionDuration',
}
export enum HealthStatsPeriodOption {
AUTO = 'auto',
TWENTY_FOUR_HOURS = '24h',
}
export type IssueOwnership = {
raw: string;
fallthrough: boolean;
dateCreated: string;
lastUpdated: string;
isActive: boolean;
autoAssignment: boolean;
};
export type CodeOwner = {
id: string;
raw: string;
dateCreated: string;
dateUpdated: string;
provider: 'github' | 'gitlab';
codeMapping?: RepositoryProjectPathConfig;
codeMappingId: string;
ownershipSyntax?: string;
errors: {
missing_external_teams: string[];
missing_external_users: string[];
missing_user_emails: string[];
teams_without_access: string[];
};
};
export type KeyValueListData = {
key: string;
subject: string;
value?: React.ReactNode;
meta?: Meta;
subjectDataTestId?: string;
subjectIcon?: React.ReactNode;
}[];
export type ExternalActorMapping = {
id: string;
externalName: string;
userId?: string;
teamId?: string;
sentryName: string;
};
export type ExternalUser = {
id: string;
memberId: string;
externalName: string;
provider: string;
integrationId: string;
};
export type ExternalTeam = {
id: string;
teamId: string;
externalName: string;
provider: string;
integrationId: string;
};
export type CodeownersFile = {
raw: string;
filepath: string;
html_url: string;
};