@@ -73,60 +73,16 @@ def serialize(
7373class GroupIntegrationDetailsEndpoint (GroupEndpoint ):
7474 owner = ApiOwner .ECOSYSTEM
7575 publish_status = {
76- "DELETE" : ApiPublishStatus .UNKNOWN ,
7776 "GET" : ApiPublishStatus .UNKNOWN ,
78- "PUT" : ApiPublishStatus .UNKNOWN ,
7977 "POST" : ApiPublishStatus .UNKNOWN ,
78+ "PUT" : ApiPublishStatus .UNKNOWN ,
79+ "DELETE" : ApiPublishStatus .UNKNOWN ,
8080 }
8181
82- def _has_issue_feature (self , organization , user ) -> bool :
83- has_issue_basic = features .has (
84- "organizations:integrations-issue-basic" , organization , actor = user
85- )
86-
87- has_issue_sync = features .has (
88- "organizations:integrations-issue-sync" , organization , actor = user
89- )
90-
91- return has_issue_sync or has_issue_basic
92-
93- def _has_issue_feature_on_integration (self , integration : RpcIntegration ) -> bool :
94- return integration .has_feature (
95- feature = IntegrationFeatures .ISSUE_BASIC
96- ) or integration .has_feature (feature = IntegrationFeatures .ISSUE_SYNC )
97-
98- def _get_installation (
99- self , integration : RpcIntegration , organization_id : int
100- ) -> IssueBasicIntegration :
101- installation = integration .get_installation (organization_id = organization_id )
102- if not isinstance (installation , IssueBasicIntegration ):
103- raise ValueError (installation )
104- return installation
105-
106- def create_issue_activity (
107- self ,
108- request : Request ,
109- group : Group ,
110- installation : IssueBasicIntegration ,
111- external_issue : ExternalIssue ,
112- new : bool ,
113- ):
114- issue_information = {
115- "title" : external_issue .title ,
116- "provider" : installation .model .get_provider ().name ,
117- "location" : installation .get_issue_url (external_issue .key ),
118- "label" : installation .get_issue_display_name (external_issue ) or external_issue .key ,
119- "new" : new ,
120- }
121- Activity .objects .create (
122- project = group .project ,
123- group = group ,
124- type = ActivityType .CREATE_ISSUE .value ,
125- user_id = request .user .id ,
126- data = issue_information ,
127- )
128-
12982 def get (self , request : Request , group , integration_id ) -> Response :
83+ """
84+ Retrieves the config needed to either link or create an external issue for a group.
85+ """
13086 if not request .user .is_authenticated :
13187 return Response (status = 400 )
13288 elif not self ._has_issue_feature (group .organization , request .user ):
@@ -175,8 +131,112 @@ def get(self, request: Request, group, integration_id) -> Response:
175131 )
176132 )
177133
178- # was thinking put for link an existing issue, post for create new issue?
134+ def post (self , request : Request , group , integration_id ) -> Response :
135+ """
136+ Creates a new external issue and links it to a group.
137+ """
138+ if not request .user .is_authenticated :
139+ return Response (status = 400 )
140+ elif not self ._has_issue_feature (group .organization , request .user ):
141+ return Response ({"detail" : MISSING_FEATURE_MESSAGE }, status = 400 )
142+
143+ organization_id = group .project .organization_id
144+ result = integration_service .organization_context (
145+ organization_id = organization_id , integration_id = integration_id
146+ )
147+ integration = result .integration
148+ org_integration = result .organization_integration
149+ if not integration or not org_integration :
150+ return Response (status = 404 )
151+
152+ if not self ._has_issue_feature_on_integration (integration ):
153+ return Response (
154+ {"detail" : "This feature is not supported for this integration." }, status = 400
155+ )
156+
157+ installation = self ._get_installation (integration , organization_id )
158+
159+ with ProjectManagementEvent (
160+ action_type = ProjectManagementActionType .CREATE_EXTERNAL_ISSUE_VIA_ISSUE_DETAIL ,
161+ integration = integration ,
162+ ).capture () as lifecycle :
163+ lifecycle .add_extras (
164+ {
165+ "provider" : integration .provider ,
166+ "integration_id" : integration .id ,
167+ }
168+ )
169+
170+ try :
171+ data = installation .create_issue (request .data )
172+ except IntegrationConfigurationError as exc :
173+ lifecycle .record_halt (exc )
174+ return Response ({"non_field_errors" : [str (exc )]}, status = 400 )
175+ except IntegrationFormError as exc :
176+ lifecycle .record_halt (exc )
177+ return Response (exc .field_errors , status = 400 )
178+ except IntegrationError as e :
179+ lifecycle .record_failure (e )
180+ return Response ({"non_field_errors" : [str (e )]}, status = 400 )
181+ except IntegrationProviderError as exc :
182+ lifecycle .record_halt (exc )
183+ return Response (
184+ {
185+ "detail" : f"Something went wrong while communicating with { integration .provider } "
186+ },
187+ status = 503 ,
188+ )
189+
190+ external_issue_key = installation .make_external_key (data )
191+ external_issue , created = ExternalIssue .objects .get_or_create (
192+ organization_id = organization_id ,
193+ integration_id = integration .id ,
194+ key = external_issue_key ,
195+ defaults = {
196+ "title" : data .get ("title" ),
197+ "description" : data .get ("description" ),
198+ "metadata" : data .get ("metadata" ),
199+ },
200+ )
201+
202+ try :
203+ with transaction .atomic (router .db_for_write (GroupLink )):
204+ GroupLink .objects .create (
205+ group_id = group .id ,
206+ project_id = group .project_id ,
207+ linked_type = GroupLink .LinkedType .issue ,
208+ linked_id = external_issue .id ,
209+ relationship = GroupLink .Relationship .references ,
210+ )
211+ except IntegrityError :
212+ return Response ({"detail" : "That issue is already linked" }, status = 400 )
213+
214+ if created :
215+ integration_issue_created .send_robust (
216+ integration = integration ,
217+ organization = group .project .organization ,
218+ user = request .user ,
219+ sender = self .__class__ ,
220+ )
221+ installation .store_issue_last_defaults (group .project , request .user , request .data )
222+
223+ self .create_issue_activity (request , group , installation , external_issue , new = True )
224+
225+ # TODO(jess): return serialized issue
226+ url = data .get ("url" ) or installation .get_issue_url (external_issue .key )
227+ context = {
228+ "id" : external_issue .id ,
229+ "key" : external_issue .key ,
230+ "url" : url ,
231+ "integrationId" : external_issue .integration_id ,
232+ "displayName" : installation .get_issue_display_name (external_issue ),
233+ }
234+ return Response (context , status = 201 )
235+
179236 def put (self , request : Request , group , integration_id ) -> Response :
237+ """
238+ Links an existing external issue to a group.
239+ """
180240 if not request .user .is_authenticated :
181241 return Response (status = 400 )
182242 elif not self ._has_issue_feature (group .organization , request .user ):
@@ -276,106 +336,10 @@ def put(self, request: Request, group, integration_id) -> Response:
276336 }
277337 return Response (context , status = 201 )
278338
279- def post (self , request : Request , group , integration_id ) -> Response :
280- if not request .user .is_authenticated :
281- return Response (status = 400 )
282- elif not self ._has_issue_feature (group .organization , request .user ):
283- return Response ({"detail" : MISSING_FEATURE_MESSAGE }, status = 400 )
284-
285- organization_id = group .project .organization_id
286- result = integration_service .organization_context (
287- organization_id = organization_id , integration_id = integration_id
288- )
289- integration = result .integration
290- org_integration = result .organization_integration
291- if not integration or not org_integration :
292- return Response (status = 404 )
293-
294- if not self ._has_issue_feature_on_integration (integration ):
295- return Response (
296- {"detail" : "This feature is not supported for this integration." }, status = 400
297- )
298-
299- installation = self ._get_installation (integration , organization_id )
300-
301- with ProjectManagementEvent (
302- action_type = ProjectManagementActionType .CREATE_EXTERNAL_ISSUE_VIA_ISSUE_DETAIL ,
303- integration = integration ,
304- ).capture () as lifecycle :
305- lifecycle .add_extras (
306- {
307- "provider" : integration .provider ,
308- "integration_id" : integration .id ,
309- }
310- )
311-
312- try :
313- data = installation .create_issue (request .data )
314- except IntegrationConfigurationError as exc :
315- lifecycle .record_halt (exc )
316- return Response ({"non_field_errors" : [str (exc )]}, status = 400 )
317- except IntegrationFormError as exc :
318- lifecycle .record_halt (exc )
319- return Response (exc .field_errors , status = 400 )
320- except IntegrationError as e :
321- lifecycle .record_failure (e )
322- return Response ({"non_field_errors" : [str (e )]}, status = 400 )
323- except IntegrationProviderError as exc :
324- lifecycle .record_halt (exc )
325- return Response (
326- {
327- "detail" : f"Something went wrong while communicating with { integration .provider } "
328- },
329- status = 503 ,
330- )
331-
332- external_issue_key = installation .make_external_key (data )
333- external_issue , created = ExternalIssue .objects .get_or_create (
334- organization_id = organization_id ,
335- integration_id = integration .id ,
336- key = external_issue_key ,
337- defaults = {
338- "title" : data .get ("title" ),
339- "description" : data .get ("description" ),
340- "metadata" : data .get ("metadata" ),
341- },
342- )
343-
344- try :
345- with transaction .atomic (router .db_for_write (GroupLink )):
346- GroupLink .objects .create (
347- group_id = group .id ,
348- project_id = group .project_id ,
349- linked_type = GroupLink .LinkedType .issue ,
350- linked_id = external_issue .id ,
351- relationship = GroupLink .Relationship .references ,
352- )
353- except IntegrityError :
354- return Response ({"detail" : "That issue is already linked" }, status = 400 )
355-
356- if created :
357- integration_issue_created .send_robust (
358- integration = integration ,
359- organization = group .project .organization ,
360- user = request .user ,
361- sender = self .__class__ ,
362- )
363- installation .store_issue_last_defaults (group .project , request .user , request .data )
364-
365- self .create_issue_activity (request , group , installation , external_issue , new = True )
366-
367- # TODO(jess): return serialized issue
368- url = data .get ("url" ) or installation .get_issue_url (external_issue .key )
369- context = {
370- "id" : external_issue .id ,
371- "key" : external_issue .key ,
372- "url" : url ,
373- "integrationId" : external_issue .integration_id ,
374- "displayName" : installation .get_issue_display_name (external_issue ),
375- }
376- return Response (context , status = 201 )
377-
378339 def delete (self , request : Request , group , integration_id ) -> Response :
340+ """
341+ Deletes a link between a group and an external issue.
342+ """
379343 if not self ._has_issue_feature (group .organization , request .user ):
380344 return Response ({"detail" : MISSING_FEATURE_MESSAGE }, status = 400 )
381345
@@ -417,3 +381,50 @@ def delete(self, request: Request, group, integration_id) -> Response:
417381 external_issue .delete ()
418382
419383 return Response (status = 204 )
384+
385+ def _has_issue_feature (self , organization , user ) -> bool :
386+ has_issue_basic = features .has (
387+ "organizations:integrations-issue-basic" , organization , actor = user
388+ )
389+
390+ has_issue_sync = features .has (
391+ "organizations:integrations-issue-sync" , organization , actor = user
392+ )
393+
394+ return has_issue_sync or has_issue_basic
395+
396+ def _has_issue_feature_on_integration (self , integration : RpcIntegration ) -> bool :
397+ return integration .has_feature (
398+ feature = IntegrationFeatures .ISSUE_BASIC
399+ ) or integration .has_feature (feature = IntegrationFeatures .ISSUE_SYNC )
400+
401+ def _get_installation (
402+ self , integration : RpcIntegration , organization_id : int
403+ ) -> IssueBasicIntegration :
404+ installation = integration .get_installation (organization_id = organization_id )
405+ if not isinstance (installation , IssueBasicIntegration ):
406+ raise ValueError (installation )
407+ return installation
408+
409+ def create_issue_activity (
410+ self ,
411+ request : Request ,
412+ group : Group ,
413+ installation : IssueBasicIntegration ,
414+ external_issue : ExternalIssue ,
415+ new : bool ,
416+ ):
417+ issue_information = {
418+ "title" : external_issue .title ,
419+ "provider" : installation .model .get_provider ().name ,
420+ "location" : installation .get_issue_url (external_issue .key ),
421+ "label" : installation .get_issue_display_name (external_issue ) or external_issue .key ,
422+ "new" : new ,
423+ }
424+ Activity .objects .create (
425+ project = group .project ,
426+ group = group ,
427+ type = ActivityType .CREATE_ISSUE .value ,
428+ user_id = request .user .id ,
429+ data = issue_information ,
430+ )
0 commit comments