Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/sentry/features/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ def register_temporary_features(manager: FeatureManager) -> None:
# API-driven integration setup pipeline (per-provider rollout)
# ...
# Project Management Integrations Feature Parity Flags
manager.add("organizations:integrations-github_enterprise-project-management", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
manager.add("organizations:integrations-gitlab-project-management", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Temporary: log full Jira Cloud `issue.updated` webhook payloads so we can design project-change link rewriting.
manager.add("organizations:jira-issue-updated-payload-logging", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
Comment on lines 137 to 142
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The check_feature_flag method in GitHubIssueSyncSpec still references the removed organizations:integrations-github_enterprise-project-management flag, which will break GitHub Enterprise issue syncing.
Severity: CRITICAL

Suggested Fix

The call to check_feature_flag() in get_resolve_sync_action() within src/sentry/integrations/github/issue_sync.py should be removed. The logic should be made unconditional to reflect the feature flag's graduation, similar to the changes made in src/sentry/integrations/utils/sync.py.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry/features/temporary.py#L137-L142

Potential issue: The removal of the
`organizations:integrations-github_enterprise-project-management` feature flag from
`src/sentry/features/temporary.py` was incomplete. A reference to this flag remains in
`src/sentry/integrations/github/issue_sync.py` within the `check_feature_flag` method.
Because the flag is no longer registered, the `features.has()` call will catch a
`FeatureNotRegistered` exception and return `False`. This causes
`get_resolve_sync_action()` to always return `ResolveSyncAction.NOOP`, which will
silently disable issue status synchronization (resolve/unresolve) from GitHub webhooks
for all GitHub Enterprise integrations.

Also affects:

  • src/sentry/integrations/utils/sync.py:43~49

Did we get this right? 👍 / 👎 to inform future reviews.

Expand Down
194 changes: 94 additions & 100 deletions src/sentry/integrations/github_enterprise/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,61 +366,58 @@ def _get_organization_config_default_values(self) -> list[dict[str, Any]]:
"""
config: list[dict[str, Any]] = []

if features.has(
"organizations:integrations-github_enterprise-project-management", self.organization
):
config.extend(
[
{
"name": self.inbound_status_key,
"type": "boolean",
"label": _("Sync GitHub Status to Sentry"),
"help": _(
"When a GitHub issue is marked closed, resolve its linked issue in Sentry. "
"When a GitHub issue is reopened, unresolve its linked Sentry issue."
),
"default": False,
},
{
"name": self.inbound_assignee_key,
"type": "boolean",
"label": _("Sync Github Assignment to Sentry"),
"help": _(
"When an issue is assigned in GitHub, assign its linked Sentry issue to the same user."
),
"default": False,
},
{
"name": self.outbound_assignee_key,
"type": "boolean",
"label": _("Sync Sentry Assignment to GitHub"),
"help": _(
"When an issue is assigned in Sentry, assign its linked GitHub issue to the same user."
),
"default": False,
},
{
"name": self.resolution_strategy_key,
"label": "Resolve",
"type": "select",
"placeholder": "Resolve",
"choices": [
("resolve", "Resolve"),
("resolve_current_release", "Resolve in Current Release"),
("resolve_next_release", "Resolve in Next Release"),
],
"help": _(
"Select what action to take on Sentry Issue when GitHub ticket is marked Closed."
),
},
{
"name": self.comment_key,
"type": "boolean",
"label": _("Sync Sentry Comments to GitHub"),
"help": _("Post comments from Sentry issues to linked GitHub issues"),
},
]
)
config.extend(
[
{
"name": self.inbound_status_key,
"type": "boolean",
"label": _("Sync GitHub Status to Sentry"),
"help": _(
"When a GitHub issue is marked closed, resolve its linked issue in Sentry. "
"When a GitHub issue is reopened, unresolve its linked Sentry issue."
),
"default": False,
},
{
"name": self.inbound_assignee_key,
"type": "boolean",
"label": _("Sync Github Assignment to Sentry"),
"help": _(
"When an issue is assigned in GitHub, assign its linked Sentry issue to the same user."
),
"default": False,
},
{
"name": self.outbound_assignee_key,
"type": "boolean",
"label": _("Sync Sentry Assignment to GitHub"),
"help": _(
"When an issue is assigned in Sentry, assign its linked GitHub issue to the same user."
),
"default": False,
},
{
"name": self.resolution_strategy_key,
"label": "Resolve",
"type": "select",
"placeholder": "Resolve",
"choices": [
("resolve", "Resolve"),
("resolve_current_release", "Resolve in Current Release"),
("resolve_next_release", "Resolve in Next Release"),
],
"help": _(
"Select what action to take on Sentry Issue when GitHub ticket is marked Closed."
),
},
{
"name": self.comment_key,
"type": "boolean",
"label": _("Sync Sentry Comments to GitHub"),
"help": _("Post comments from Sentry issues to linked GitHub issues"),
},
]
)

return config

Expand All @@ -430,55 +427,52 @@ def get_organization_config(self) -> list[dict[str, Any]]:
"""
config = self._get_organization_config_default_values()

if features.has(
"organizations:integrations-github_enterprise-project-management", self.organization
):
# Async lookup for integration external projects in the frontend
# Get currently configured external projects to display their labels
current_repo_items = []
external_projects = IntegrationExternalProject.objects.filter(
organization_integration_id=self.org_integration.id
)
# Async lookup for integration external projects in the frontend
# Get currently configured external projects to display their labels
current_repo_items = []
external_projects = IntegrationExternalProject.objects.filter(
organization_integration_id=self.org_integration.id
)

if external_projects.exists():
# Use the stored external_id from IntegrationExternalProject
current_repo_items = [
{"value": project.external_id, "label": project.external_id}
for project in external_projects
]
if external_projects.exists():
# Use the stored external_id from IntegrationExternalProject
current_repo_items = [
{"value": project.external_id, "label": project.external_id}
for project in external_projects
]

config.insert(
0,
{
"name": self.outbound_status_key,
"type": "choice_mapper",
"label": _("Sync Sentry Status to Github"),
"help": _(
"When a Sentry issue changes status, change the status of the linked ticket in Github."
config.insert(
0,
{
"name": self.outbound_status_key,
"type": "choice_mapper",
"label": _("Sync Sentry Status to Github"),
"help": _(
"When a Sentry issue changes status, change the status of the linked ticket in Github."
),
"addButtonText": _("Add Github Project"),
"addDropdown": {
"emptyMessage": _("All projects configured"),
"noResultsMessage": _("Could not find Github project"),
"items": current_repo_items,
"url": reverse(
"sentry-integration-github-search",
args=[self.organization.slug, self.model.id],
),
"addButtonText": _("Add Github Project"),
"addDropdown": {
"emptyMessage": _("All projects configured"),
"noResultsMessage": _("Could not find Github project"),
"items": current_repo_items,
"url": reverse(
"sentry-integration-github-search",
args=[self.organization.slug, self.model.id],
),
"searchField": "repo",
},
"mappedSelectors": {
"on_resolve": {"choices": GitHubIssueStatus.get_choices()},
"on_unresolve": {"choices": GitHubIssueStatus.get_choices()},
},
"columnLabels": {
"on_resolve": _("When resolved"),
"on_unresolve": _("When unresolved"),
},
"mappedColumnLabel": _("Github Project"),
"formatMessageValue": False,
"searchField": "repo",
},
)
"mappedSelectors": {
"on_resolve": {"choices": GitHubIssueStatus.get_choices()},
"on_unresolve": {"choices": GitHubIssueStatus.get_choices()},
},
"columnLabels": {
"on_resolve": _("When resolved"),
"on_unresolve": _("When unresolved"),
},
"mappedColumnLabel": _("Github Project"),
"formatMessageValue": False,
},
)

context = organization_service.get_organization_by_id(
id=self.organization_id, include_projects=False, include_teams=False
Expand Down
4 changes: 1 addition & 3 deletions src/sentry/integrations/utils/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ def should_sync_assignee_inbound(
if provider == "github":
return True
elif provider == "github_enterprise":
return features.has(
"organizations:integrations-github_enterprise-project-management", organization
)
return True
elif provider == "gitlab":
return features.has("organizations:integrations-gitlab-project-management", organization)
return True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,6 @@ def test_extract_source_path_from_source_url(self) -> None:
)

@responses.activate
@with_feature("organizations:integrations-github_enterprise-project-management")
def test_get_organization_config(self) -> None:
# Mock the repositories endpoint
responses.add(
Expand Down
Loading