From 112f7667851da54478e7b18b7848297a644d8a29 Mon Sep 17 00:00:00 2001 From: mujacica Date: Tue, 11 Nov 2025 19:30:09 +0100 Subject: [PATCH 1/3] feat(perforce): Add frontend support for Perforce integration This commit adds frontend support for Perforce version control integration: - New Perforce icon component and assets - Integration icon and plugin support - Updated repository project path config form to support Perforce - Type definitions for Perforce integration The frontend changes enable: - Visual representation of Perforce integration in the UI - Configuration UI for repository and code mappings - Integration with existing VCS provider infrastructure --- static/app/icons/iconPerforce.tsx | 11 ++++++++++ static/app/icons/index.tsx | 1 + static/app/plugins/components/pluginIcon.tsx | 2 ++ static/app/types/integrations.tsx | 2 +- static/app/utils/integrationUtil.tsx | 7 ++++++ .../repositoryProjectPathConfigForm.tsx | 22 +++++++++++++------ static/images/integrations/perforce.svg | 6 +++++ 7 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 static/app/icons/iconPerforce.tsx create mode 100644 static/images/integrations/perforce.svg diff --git a/static/app/icons/iconPerforce.tsx b/static/app/icons/iconPerforce.tsx new file mode 100644 index 00000000000000..6213efac52b4d6 --- /dev/null +++ b/static/app/icons/iconPerforce.tsx @@ -0,0 +1,11 @@ +import type {SVGIconProps} from './svgIcon'; +import {SvgIcon} from './svgIcon'; + +export function IconPerforce(props: SVGIconProps) { + return ( + + + + + ); +} diff --git a/static/app/icons/index.tsx b/static/app/icons/index.tsx index db9dbe8842decf..5f39dbc9541ec8 100644 --- a/static/app/icons/index.tsx +++ b/static/app/icons/index.tsx @@ -85,6 +85,7 @@ export {IconNumber} from './iconNumber'; export {IconOpen} from './iconOpen'; export {IconPanel} from './iconPanel'; export {IconPause} from './iconPause'; +export {IconPerforce} from './iconPerforce'; export {IconPin} from './iconPin'; export {IconPlay} from './iconPlay'; export {IconPrevent} from './iconPrevent'; diff --git a/static/app/plugins/components/pluginIcon.tsx b/static/app/plugins/components/pluginIcon.tsx index 8736193dc69520..b413d24a72d0e4 100644 --- a/static/app/plugins/components/pluginIcon.tsx +++ b/static/app/plugins/components/pluginIcon.tsx @@ -17,6 +17,7 @@ import jumpcloud from 'sentry-logos/logo-jumpcloud.svg'; import msteams from 'sentry-logos/logo-msteams.svg'; import opsgenie from 'sentry-logos/logo-opsgenie.svg'; import pagerduty from 'sentry-logos/logo-pagerduty.svg'; +import perforce from 'sentry-logos/logo-perforce.svg'; import pivotal from 'sentry-logos/logo-pivotaltracker.svg'; import pushover from 'sentry-logos/logo-pushover.svg'; import redmine from 'sentry-logos/logo-redmine.svg'; @@ -57,6 +58,7 @@ const PLUGIN_ICONS = { msteams, opsgenie, pagerduty, + perforce, pivotal, pushover, redmine, diff --git a/static/app/types/integrations.tsx b/static/app/types/integrations.tsx index 0f47431aa62efa..97582eaa517da6 100644 --- a/static/app/types/integrations.tsx +++ b/static/app/types/integrations.tsx @@ -577,7 +577,7 @@ export type CodeOwner = { users_without_access: string[]; }; id: string; - provider: 'github' | 'gitlab'; + provider: 'github' | 'gitlab' | 'perforce'; raw: string; codeMapping?: RepositoryProjectPathConfig; ownershipSyntax?: string; diff --git a/static/app/utils/integrationUtil.tsx b/static/app/utils/integrationUtil.tsx index b71d10a168f486..1f7462cbfca238 100644 --- a/static/app/utils/integrationUtil.tsx +++ b/static/app/utils/integrationUtil.tsx @@ -9,6 +9,7 @@ import { IconGithub, IconGitlab, IconJira, + IconPerforce, IconSentry, IconVsts, } from 'sentry/icons'; @@ -206,6 +207,8 @@ export const getIntegrationIcon = ( case 'jira': case 'jira_server': return ; + case 'perforce': + return ; case 'vsts': return ; case 'codecov': @@ -230,6 +233,8 @@ export const getIntegrationDisplayName = (integrationType?: string) => { case 'jira': case 'jira_server': return 'Jira'; + case 'perforce': + return 'Perforce'; case 'vsts': return 'Azure DevOps'; case 'codecov': @@ -279,6 +284,8 @@ export function getCodeOwnerIcon( return ; case 'gitlab': return ; + case 'perforce': + return ; default: return ; } diff --git a/static/app/views/settings/organizationIntegrations/repositoryProjectPathConfigForm.tsx b/static/app/views/settings/organizationIntegrations/repositoryProjectPathConfigForm.tsx index 7f3f322e1ca036..632d11db119dd7 100644 --- a/static/app/views/settings/organizationIntegrations/repositoryProjectPathConfigForm.tsx +++ b/static/app/views/settings/organizationIntegrations/repositoryProjectPathConfigForm.tsx @@ -58,6 +58,8 @@ function RepositoryProjectPathConfigForm({ } ); + const isPerforce = integration.provider.key === 'perforce'; + // Effect to handle the case when integration repos data becomes available useEffect(() => { if (integrationReposData?.repos && selectedRepo) { @@ -93,13 +95,19 @@ function RepositoryProjectPathConfigForm({ { name: 'defaultBranch', type: 'string', - required: true, - label: t('Branch'), - placeholder: t('Type your branch'), + required: !isPerforce, + label: isPerforce ? t('Stream') : t('Branch'), + placeholder: isPerforce + ? t('Type your stream (optional, e.g., main)') + : t('Type your branch'), showHelpInTooltip: true, - help: t( - 'If an event does not have a release tied to a commit, we will use this branch when linking to your source code.' - ), + help: isPerforce + ? t( + 'Optional: Specify a stream/codeline (e.g., "main"). If not specified, the depot root will be used. Streams are part of the depot path in Perforce.' + ) + : t( + 'If an event does not have a release tied to a commit, we will use this branch when linking to your source code.' + ), }, { name: 'stackRoot', @@ -135,7 +143,7 @@ function RepositoryProjectPathConfigForm({ } const initialData = { - defaultBranch: 'main', + defaultBranch: isPerforce ? '' : 'main', stackRoot: '', sourceRoot: '', repositoryId: existingConfig?.repoId, diff --git a/static/images/integrations/perforce.svg b/static/images/integrations/perforce.svg new file mode 100644 index 00000000000000..e640a6e7469cb0 --- /dev/null +++ b/static/images/integrations/perforce.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 972e0606ae1f6d4b2acf99859cb083a3fa0ca3cc Mon Sep 17 00:00:00 2001 From: mujacica Date: Tue, 11 Nov 2025 19:37:16 +0100 Subject: [PATCH 2/3] Add logo from backend for build to work --- src/sentry/static/sentry/images/logos/logo-perforce.svg | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/sentry/static/sentry/images/logos/logo-perforce.svg diff --git a/src/sentry/static/sentry/images/logos/logo-perforce.svg b/src/sentry/static/sentry/images/logos/logo-perforce.svg new file mode 100644 index 00000000000000..e640a6e7469cb0 --- /dev/null +++ b/src/sentry/static/sentry/images/logos/logo-perforce.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 8061c4a8437a7538d76ce58f08ba81dacd3f2fc2 Mon Sep 17 00:00:00 2001 From: mujacica Date: Thu, 13 Nov 2025 12:15:09 +0100 Subject: [PATCH 3/3] PR Comment Fixes --- .../static/sentry/images/logos/logo-perforce.svg | 11 +++++------ static/app/icons/iconPerforce.tsx | 5 ++--- .../repositoryProjectPathConfigForm.tsx | 14 ++++++++------ static/images/integrations/perforce.svg | 11 +++++------ 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/sentry/static/sentry/images/logos/logo-perforce.svg b/src/sentry/static/sentry/images/logos/logo-perforce.svg index e640a6e7469cb0..eb8c0c234101f5 100644 --- a/src/sentry/static/sentry/images/logos/logo-perforce.svg +++ b/src/sentry/static/sentry/images/logos/logo-perforce.svg @@ -1,6 +1,5 @@ - - - - - - \ No newline at end of file + + + + + diff --git a/static/app/icons/iconPerforce.tsx b/static/app/icons/iconPerforce.tsx index 6213efac52b4d6..73512b9bba1766 100644 --- a/static/app/icons/iconPerforce.tsx +++ b/static/app/icons/iconPerforce.tsx @@ -3,9 +3,8 @@ import {SvgIcon} from './svgIcon'; export function IconPerforce(props: SVGIconProps) { return ( - - - + + ); } diff --git a/static/app/views/settings/organizationIntegrations/repositoryProjectPathConfigForm.tsx b/static/app/views/settings/organizationIntegrations/repositoryProjectPathConfigForm.tsx index 632d11db119dd7..ca42e505d1fe02 100644 --- a/static/app/views/settings/organizationIntegrations/repositoryProjectPathConfigForm.tsx +++ b/static/app/views/settings/organizationIntegrations/repositoryProjectPathConfigForm.tsx @@ -58,7 +58,9 @@ function RepositoryProjectPathConfigForm({ } ); - const isPerforce = integration.provider.key === 'perforce'; + // Stream-based VCS (like Perforce) use streams/codelines instead of branches + // and don't require a default branch to be specified + const isStreamBased = integration.provider.key === 'perforce'; // Effect to handle the case when integration repos data becomes available useEffect(() => { @@ -95,13 +97,13 @@ function RepositoryProjectPathConfigForm({ { name: 'defaultBranch', type: 'string', - required: !isPerforce, - label: isPerforce ? t('Stream') : t('Branch'), - placeholder: isPerforce + required: !isStreamBased, + label: isStreamBased ? t('Stream') : t('Branch'), + placeholder: isStreamBased ? t('Type your stream (optional, e.g., main)') : t('Type your branch'), showHelpInTooltip: true, - help: isPerforce + help: isStreamBased ? t( 'Optional: Specify a stream/codeline (e.g., "main"). If not specified, the depot root will be used. Streams are part of the depot path in Perforce.' ) @@ -143,7 +145,7 @@ function RepositoryProjectPathConfigForm({ } const initialData = { - defaultBranch: isPerforce ? '' : 'main', + defaultBranch: isStreamBased ? '' : 'main', stackRoot: '', sourceRoot: '', repositoryId: existingConfig?.repoId, diff --git a/static/images/integrations/perforce.svg b/static/images/integrations/perforce.svg index e640a6e7469cb0..eb8c0c234101f5 100644 --- a/static/images/integrations/perforce.svg +++ b/static/images/integrations/perforce.svg @@ -1,6 +1,5 @@ - - - - - - \ No newline at end of file + + + + +