Skip to content

Commit ffe2b2f

Browse files
authored
Merge pull request #1547 from the-deep/feature/clone-analysis-mutation
Add clone analysis mutation
2 parents eeb0e1a + b1ade10 commit ffe2b2f

File tree

4 files changed

+146
-1
lines changed

4 files changed

+146
-1
lines changed

apps/analysis/mutation.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
AnalysisType,
3636
)
3737
from .serializers import (
38+
AnalysisCloneGqlSerializer,
3839
AnalysisPillarGqlSerializer,
3940
DiscardedEntryGqlSerializer,
4041
AnalysisTopicModelSerializer,
@@ -113,6 +114,11 @@
113114
serializer_class=AnalysisGqlSerializer,
114115
)
115116

117+
AnalysisCloneInputType = generate_input_type_for_serializer(
118+
'AnalysisCloneInputType',
119+
serializer_class=AnalysisCloneGqlSerializer
120+
)
121+
116122

117123
class RequiredPermissionMixin():
118124
permissions = [
@@ -314,6 +320,14 @@ class Arguments:
314320
result = graphene.Field(AnalysisPillarType)
315321

316322

323+
class AnalysisClone(AnalysisMutationMixin, PsGrapheneMutation):
324+
class Arguments:
325+
data = AnalysisCloneInputType(required=True)
326+
model = Analysis
327+
serializer_class = AnalysisCloneGqlSerializer
328+
result = graphene.Field(AnalysisType)
329+
330+
317331
class Mutation():
318332
# Analysis Pillar
319333
analysis_pillar_update = UpdateAnalysisPillar.Field()
@@ -339,3 +353,6 @@ class Mutation():
339353
analysis_create = CreateAnalysis.Field()
340354
analysis_update = UpdateAnalysis.Field()
341355
analysis_delete = DeleteAnalysis.Field()
356+
357+
# AnalysisClone
358+
analysis_clone = AnalysisClone.Field()

apps/analysis/serializers.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,37 @@ def update(self, instance, validated_data):
479479
return super().update(instance, validated_data)
480480

481481

482-
AnalysisCloneGqlSerializer = AnalysisCloneInputSerializer
482+
class AnalysisCloneGqlSerializer(serializers.Serializer):
483+
analysis_id = IntegerIDField()
484+
title = serializers.CharField(required=True, write_only=True)
485+
start_date = serializers.DateField(write_only=True, required=False, allow_null=True)
486+
end_date = serializers.DateField(required=True, write_only=True)
487+
488+
def validate(self, data):
489+
start_date = data.get('start_date')
490+
end_date = data.get('end_date')
491+
if start_date and start_date > end_date:
492+
raise serializers.ValidationError(
493+
{'end_date': 'End date must occur after start date'}
494+
)
495+
return data
496+
497+
def validate_analysis_id(self, analysis_id):
498+
analysis = Analysis.objects.filter(
499+
project=self.context['request'].active_project,
500+
pk=analysis_id
501+
).first()
502+
if analysis is None:
503+
raise serializers.ValidationError("Analysis does not exists")
504+
return analysis
505+
506+
def create(self, validated_data):
507+
title = validated_data['title']
508+
end_date = validated_data['end_date']
509+
# NOTE validated_data['analysis_id'] is an object of analysis
510+
analysis = validated_data['analysis_id']
511+
analysis.clone_analysis(title, end_date)
512+
return analysis
483513

484514

485515
class AnalysisTopicModelSerializer(UserResourceSerializer, serializers.ModelSerializer):

apps/analysis/tests/test_mutations.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
AnalysisPillarFactory,
2020
AnalysisReportFactory,
2121
AnalysisReportUploadFactory,
22+
AnalyticalStatementFactory,
23+
DiscardedEntryFactory,
2224
)
2325

2426
from analysis.models import (
27+
DiscardedEntry,
2528
TopicModel,
2629
TopicModelCluster,
2730
AutomaticSummary,
@@ -1732,3 +1735,84 @@ def _query_check(**kwargs):
17321735
self.assertEqual(analysis_pillar_resp_data['title'], self.update_minput['analysisPillarUpdate']['title'])
17331736
self.assertEqual(analysis_pillar_resp_data['id'], str(self.update_minput['analysisPillarID']))
17341737
self.assertEqual(analysis_pillar_resp_data['analysisId'], str(self.analysis.id))
1738+
1739+
1740+
class TestCloneAnalysisMutationSchema(GraphQLTestCase):
1741+
ANALYSIS_CLONE_MUTATION = '''
1742+
mutation AnalysisClone($projectId: ID!, $data: AnalysisCloneInputType!) {
1743+
project(id: $projectId) {
1744+
analysisClone(data: $data) {
1745+
ok
1746+
errors
1747+
result {
1748+
id
1749+
title
1750+
endDate
1751+
}
1752+
__typename
1753+
}
1754+
__typename
1755+
}
1756+
}
1757+
'''
1758+
1759+
def setUp(self):
1760+
super().setUp()
1761+
self.project = ProjectFactory.create()
1762+
self.member_user = UserFactory.create()
1763+
self.non_member_user = UserFactory.create()
1764+
self.readonly_member_user = UserFactory.create()
1765+
self.project.add_member(self.readonly_member_user, role=self.project_role_reader_non_confidential)
1766+
af = AnalysisFrameworkFactory.create()
1767+
project = ProjectFactory.create(analysis_framework=af)
1768+
lead = LeadFactory.create(project=project)
1769+
self.project.add_member(self.member_user, role=self.project_role_member)
1770+
entry = EntryFactory.create(project=project, lead=lead)
1771+
EntryFactory.create(project=project, lead=lead)
1772+
self.analysis = AnalysisFactory.create(
1773+
project=project,
1774+
team_lead=self.member_user,
1775+
end_date=datetime.date(2022, 4, 1),
1776+
1777+
)
1778+
pillar = AnalysisPillarFactory.create(analysis=self.analysis, title='title1', assignee=self.member_user)
1779+
AnalyticalStatementFactory.create(
1780+
analysis_pillar=pillar,
1781+
statement='Hello from here',
1782+
client_id='1',
1783+
)
1784+
DiscardedEntryFactory.create(
1785+
entry=entry,
1786+
analysis_pillar=pillar,
1787+
tag=DiscardedEntry.TagType.REDUNDANT
1788+
)
1789+
1790+
def test_clone_analysis(self):
1791+
def _query_check(**kwargs):
1792+
return self.query_check(
1793+
self.ANALYSIS_CLONE_MUTATION,
1794+
variables=self.minput,
1795+
**kwargs
1796+
)
1797+
self.minput = dict(
1798+
data=dict(
1799+
analysisId=self.analysis.id,
1800+
title='cloned_title',
1801+
endDate="2022-04-01",
1802+
),
1803+
projectId=self.project.id,
1804+
)
1805+
# without login
1806+
_query_check(assert_for_error=True)
1807+
1808+
# With login (non-member)
1809+
self.force_login(self.non_member_user)
1810+
_query_check(assert_for_error=True)
1811+
1812+
# member user (read-only)
1813+
self.force_login(self.readonly_member_user)
1814+
_query_check(assert_for_error=True)
1815+
1816+
# member user
1817+
self.force_login(self.member_user)
1818+
_query_check(assert_for_error=False)

schema.graphql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,19 @@ type AnalysisAutomaticSummaryType {
7171
status: AutomaticSummaryStatusEnum!
7272
}
7373

74+
type AnalysisClone {
75+
errors: [GenericScalar!]
76+
ok: Boolean
77+
result: AnalysisType
78+
}
79+
80+
input AnalysisCloneInputType {
81+
analysisId: ID!
82+
title: String!
83+
startDate: Date
84+
endDate: Date!
85+
}
86+
7487
input AnalysisFrameworkCloneInputType {
7588
afId: ID!
7689
title: String!
@@ -5521,6 +5534,7 @@ type ProjectMutationType {
55215534
analysisCreate(data: AnalysisInputType!): CreateAnalysis
55225535
analysisUpdate(data: AnalysisInputType!, id: ID!): UpdateAnalysis
55235536
analysisDelete(id: ID!): DeleteAnalysis
5537+
analysisClone(data: AnalysisCloneInputType!): AnalysisClone
55245538
exportCreate(data: ExportCreateInputType!): CreateUserExport
55255539
exportUpdate(data: ExportUpdateInputType!, id: ID!): UpdateUserExport
55265540
exportCancel(id: ID!): CancelUserExport

0 commit comments

Comments
 (0)