def serialize(self, obj: Integration, attrs: Mapping[str, Any], user: User, **kwargs: Any) -> MutableMapping[str, JSONData]: provider = obj.get_provider() return { "id": str(obj.id), "name": obj.name, "icon": obj.metadata.get("icon"), "domainName": obj.metadata.get("domain_name"), "accountType": obj.metadata.get("account_type"), "status": obj.get_status_display(), "provider": serialize_provider(provider), }
def serialize( self, obj: Integration, attrs: Mapping[str, Any], user: User, include_config: bool = True, **kwargs: Any, ) -> MutableMapping[str, JSONData]: data = super().serialize(obj, attrs, user) if not include_config: return data data.update({"configOrganization": []}) try: install = obj.get_installation( organization_id=self.organization_id) except NotImplementedError: # The integration may not implement a Installed Integration object # representation. pass else: data.update( {"configOrganization": install.get_organization_config()}) # Query param "action" only attached in TicketRuleForm modal. if self.params.get("action") == "create": data["createIssueConfig"] = install.get_create_issue_config( None, user, params=self.params) return data
def serialize( self, obj: Integration, attrs: Mapping[str, Any], user: User, include_config: bool = True ) -> MutableMapping[str, JSONData]: # XXX(epurkhiser): This is O(n) for integrations, especially since # we're using the IntegrationConfigSerializer which pulls in the # integration installation config object which very well may be making # API request for config options. integration: MutableMapping[str, Any] = serialize( objects=obj.integration, user=user, serializer=IntegrationConfigSerializer(obj.organization.id, params=self.params), include_config=include_config, ) dynamic_display_information = None config_data = None try: installation = obj.integration.get_installation(obj.organization_id) except NotImplementedError: # slack doesn't have an installation implementation config_data = obj.config if include_config else None else: try: # just doing this to avoid querying for an object we already have installation._org_integration = obj config_data = installation.get_config_data() if include_config else None dynamic_display_information = installation.get_dynamic_display_information() except ApiError as e: # If there is an ApiError from our 3rd party integration # providers, assume there is an problem with the configuration # and set it to disabled. integration.update({"status": "disabled"}) name = "sentry.serializers.model.organizationintegration" log_info = { "error": str(e), "integration_id": obj.integration.id, "integration_provider": obj.integration.provider, } logger.info(name, extra=log_info) integration.update( { "configData": config_data, "externalId": obj.integration.external_id, "organizationId": obj.organization.id, "organizationIntegrationStatus": obj.get_status_display(), "gracePeriodEnd": obj.grace_period_end, } ) if dynamic_display_information: integration.update({"dynamicDisplayInformation": dynamic_display_information}) return integration
def _handle_delete(self, event: Mapping[str, Any], integration: Integration) -> None: organizations = integration.organizations.all() logger.info( "InstallationEventWebhook._handle_delete", extra={ "external_id": event["installation"]["id"], "integration_id": integration.id, "organization_id_list": organizations.values_list("id", flat=True), }, ) integration.update(status=ObjectStatus.DISABLED) Repository.objects.filter( organization_id__in=organizations.values_list("id", flat=True), provider=f"integrations:{self.provider}", integration_id=integration.id, ).update(status=ObjectStatus.DISABLED)
def serialize( self, obj: Integration, attrs: Mapping[str, Any], user: User, **kwargs: Any ) -> MutableMapping[str, JSONData]: data = super().serialize(obj, attrs, user) organization_id = kwargs.pop("organization_id") installation = obj.get_installation(organization_id) if self.action == "link": config = installation.get_link_issue_config(self.group, params=self.params) data["linkIssueConfig"] = config if self.action == "create": config = installation.get_create_issue_config(self.group, user, params=self.params) data["createIssueConfig"] = config return data
def handle_status_change( integration: Integration, external_issue_key: str, status_change: Mapping[str, str] | None, project: str | None, ) -> None: if status_change is None: return for installation in integration.get_installations(): installation.sync_status_inbound( external_issue_key, { "new_state": status_change["newValue"], # old_state is None when the issue is New "old_state": status_change.get("oldValue"), "project": project, }, )
def where_should_sync( integration: Integration, key: str, organization_id: int | None = None, ) -> Sequence[Organization]: """ Given an integration, get the list of organizations where the sync type in `key` is enabled. If an optional `organization_id` is passed, then only check the integration for that organization. """ kwargs = dict() if organization_id: kwargs["id"] = organization_id return [ organization for organization in integration.organizations.filter(**kwargs) if features.has("organizations:integrations-issue-sync", organization) and integration.get_installation(organization.id).should_sync(key) ]
def handle_status_change( self, integration: Integration, external_issue_key: str, status_change: Optional[Mapping[str, str]], project: Optional[str], ) -> None: if status_change is None: return organization_ids = OrganizationIntegration.objects.filter( integration_id=integration.id).values_list("organization_id", flat=True) for organization_id in organization_ids: installation = integration.get_installation(organization_id) data = { "new_state": status_change["newValue"], # old_state is None when the issue is New "old_state": status_change.get("oldValue"), "project": project, } installation.sync_status_inbound(external_issue_key, data)
def _handle( self, integration: Integration, event: Mapping[str, Any], organization: Organization, repo: Repository, host: str | None = None, ) -> None: authors = {} client = integration.get_installation( organization_id=organization.id).get_client() gh_username_cache: MutableMapping[str, str | None] = {} for commit in event["commits"]: if not commit["distinct"]: continue if self.should_ignore_commit(commit): continue author_email = commit["author"]["email"] if "@" not in author_email: author_email = f"{author_email[:65]}@localhost" # try to figure out who anonymous emails are elif self.is_anonymous_email(author_email): gh_username: str | None = commit["author"].get("username") # bot users don't have usernames if gh_username: external_id = self.get_external_id(gh_username) if gh_username in gh_username_cache: author_email = gh_username_cache[ gh_username] or author_email else: try: commit_author = CommitAuthor.objects.get( external_id=external_id, organization_id=organization.id) except CommitAuthor.DoesNotExist: commit_author = None if commit_author is not None and not self.is_anonymous_email( commit_author.email): author_email = commit_author.email gh_username_cache[gh_username] = author_email else: try: gh_user = client.get_user(gh_username) except ApiError as exc: logger.exception(str(exc)) else: # even if we can't find a user, set to none so we # don't re-query gh_username_cache[gh_username] = None try: identity = Identity.objects.get( external_id=gh_user["id"], idp__type=self.provider, idp__external_id=self. get_idp_external_id(integration, host), ) except Identity.DoesNotExist: pass else: author_email = identity.user.email gh_username_cache[ gh_username] = author_email if commit_author is not None: try: with transaction.atomic(): commit_author.update( email=author_email, external_id=external_id) except IntegrityError: pass if commit_author is not None: authors[author_email] = commit_author # TODO(dcramer): we need to deal with bad values here, but since # its optional, lets just throw it out for now if len(author_email) > 75: author = None elif author_email not in authors: authors[ author_email] = author = CommitAuthor.objects.get_or_create( organization_id=organization.id, email=author_email, defaults={"name": commit["author"]["name"][:128]}, )[0] update_kwargs = {} if author.name != commit["author"]["name"][:128]: update_kwargs["name"] = commit["author"]["name"][:128] gh_username = commit["author"].get("username") if gh_username: external_id = self.get_external_id(gh_username) if author.external_id != external_id and not self.is_anonymous_email( author.email): update_kwargs["external_id"] = external_id if update_kwargs: try: with transaction.atomic(): author.update(**update_kwargs) except IntegrityError: pass else: author = authors[author_email] try: with transaction.atomic(): c = Commit.objects.create( repository_id=repo.id, organization_id=organization.id, key=commit["id"], message=commit["message"], author=author, date_added=parse_date(commit["timestamp"]).astimezone( timezone.utc), ) for fname in commit["added"]: CommitFileChange.objects.create( organization_id=organization.id, commit=c, filename=fname, type="A") for fname in commit["removed"]: CommitFileChange.objects.create( organization_id=organization.id, commit=c, filename=fname, type="D") for fname in commit["modified"]: CommitFileChange.objects.create( organization_id=organization.id, commit=c, filename=fname, type="M") except IntegrityError: pass