Merge lp:~cprov/uci-engine/ts-reviews-ng into lp:uci-engine

Proposed by Celso Providelo
Status: Merged
Approved by: Celso Providelo
Approved revision: 886
Merged at revision: 885
Proposed branch: lp:~cprov/uci-engine/ts-reviews-ng
Merge into: lp:uci-engine
Diff against target: 678 lines (+445/-24)
9 files modified
ci-utils/ci_utils/ticket_states.py (+11/-0)
ticket_system/ticket/api.py (+18/-1)
ticket_system/ticket/migrations/0019_auto__add_field_review_status__chg_field_review_review_type.py (+120/-0)
ticket_system/ticket/migrations/0020_fill_review_status.py (+119/-0)
ticket_system/ticket/migrations/0021_auto__del_field_review_completed.py (+113/-0)
ticket_system/ticket/models.py (+27/-10)
ticket_system/ticket/tests/test_models.py (+22/-1)
ticket_system/ticket/tests/test_read_api.py (+4/-4)
ticket_system/ticket/tests/test_write_api.py (+11/-8)
To merge this branch: bzr merge lp:~cprov/uci-engine/ts-reviews-ng
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Ursula Junque (community) Approve
Review via email: mp+240798@code.launchpad.net

Commit message

Update TS model to support review 'status' instead of simple 'completed' flag.

Description of the change

Update TS model to support review 'status' instead of simple 'completed' flag.

Now review.status can hold PENDING (default), APPROVED and DISAPPROVED, similarly to LP MP votes.

The reviews in the webui will be broken for a short while, but a subsequent MP is adjusting it and adding tests.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:884
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1676/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1676/rebuild

review: Approve (continuous-integration)
Revision history for this message
Ursula Junque (ursinha) wrote :

I have some comments below, regarding the use of the status you added:

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:885
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1677/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1677/rebuild

review: Approve (continuous-integration)
Revision history for this message
Ursula Junque (ursinha) wrote :

As we discussed via IRC, if you are able to leave the review status easily editable before the ticket step changes, then you could go with the NEEDS_* options. I like the idea of having status similar to the MPs, so I won't say no to that, I was just trying to avoid confusion for the user whenever reviewing to a "transitional" status.

Revision history for this message
Celso Providelo (cprov) wrote :

We will be in a better position to decide if we are already in shape to add extra review status after working out the UI for them. No worries, let's go with it as it is and unblock the subsequent tasks.

Revision history for this message
Ursula Junque (ursinha) :
review: Approve
Revision history for this message
Ubuntu CI Bot (uci-bot) wrote :
Download full text (58.4 KiB)

The attempt to merge lp:~cprov/uci-engine/ts-reviews-ng into lp:uci-engine failed. Below is the output from the failed tests.

Running cm...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
uploading webui-content.tgz to swift
Updating source dependencies...
2014-11-06 04:15:04 INFO juju.cmd supercommand.go:37 running jujud [1.20.11.1-precise-amd64 gc]
2014-11-06 04:15:04 DEBUG juju.agent agent.go:377 read agent config, format "1.18"
2014-11-06 04:15:04 INFO juju.jujud unit.go:78 unit agent unit-ci-airline-rabbit-0 start (1.20.11.1-precise-amd64 [gc])
2014-11-06 04:15:04 INFO juju.worker runner.go:260 start "api"
2014-11-06 04:15:04 INFO juju.state.api apiclient.go:242 dialing "wss://10.0.3.1:17070/"
2014-11-06 04:15:04 INFO juju.state.api apiclient.go:176 connection established to "wss://10.0.3.1:17070/"
2014-11-06 04:15:05 INFO juju.state.api apiclient.go:242 dialing "wss://10.0.3.1:17070/"
2014-11-06 04:15:05 INFO juju.state.api apiclient.go:176 connection established to "wss://10.0.3.1:17070/"
2014-11-06 04:15:05 INFO juju.state.api apiclient.go:242 dialing "wss://10.0.3.1:17070/"
2014-11-06 04:15:05 INFO juju.state.api apiclient.go:176 connection established to "wss://10.0.3.1:17070/"
2014-11-06 04:15:05 INFO juju.worker runner.go:260 start "upgrader"
2014-11-06 04:15:05 INFO juju.worker runner.go:260 start "logger"
2014-11-06 04:15:05 DEBUG juju.worker.logger logger.go:35 initial log config: "<root>=DEBUG"
2014-11-06 04:15:05 INFO juju.worker runner.go:260 start "uniter"
2014-11-06 04:15:05 DEBUG juju.worker.logger logger.go:60 logger setup
2014-11-06 04:15:05 INFO juju.worker runner.go:260 start "apiaddressupdater"
2014-11-06 04:15:05 INFO juju.worker runner.go:260 start "rsyslog"
2014-11-06 04:15:05 DEBUG juju.worker.rsyslog worker.go:75 starting rsyslog worker mode 1 for "unit-ci-airline-rabbit-0" "tarmac-local"
2014-11-06 04:15:05 DEBUG juju.worker.logger logger.go:45 reconfiguring logging from "<root>=DEBUG" to "<root>=WARNING;unit=DEBUG"
2014-11-06 04:15:10 INFO juju-log Installing python-jinja2 with options: ['--option=Dpkg::Options::=--force-confold']
2014-11-06 04:15:57 INFO install Reading package lists...
2014-11-06 04:15:58 INFO install Building dependency tree...
2014-11-06 04:15:58 INFO install Reading state information...
2014-11-06 04:15:59 INFO install The following extra packages will be installed:
2014-11-06 04:15:59 INFO install python-markupsafe
2014-11-06 04:15:59 INFO install Suggested packages:
2014-11-06 04:15:59 INFO install python-jinja2-doc
2014-11-06 04:15:59 INFO install The following NEW packages will be installed:
2014-11-06 04:15:59 INFO install python-jinja2 python-markupsafe
2014-11-06 04:15:59 INFO install 0 upgraded, 2 newly installed, 0 to remove and 3 not upgraded.
2014-11-06 04:15:59 INFO install Need to get 172 kB of archives.
2014-11-06 04:15:59 INFO install After this operation, 1124 kB of additional disk space will be used.
2014-11-06 04:15:59 INFO install Get:1 http://archive.ubuntu.com/ubuntu/ precise/main python-...

lp:~cprov/uci-engine/ts-reviews-ng updated
886. By Celso Providelo

Fixing Review representation and add a test.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:886
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1681/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1681/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ci-utils/ci_utils/ticket_states.py'
2--- ci-utils/ci_utils/ticket_states.py 2014-10-23 01:21:04 +0000
3+++ ci-utils/ci_utils/ticket_states.py 2014-11-06 04:57:04 +0000
4@@ -80,6 +80,17 @@
5 def __str__(self):
6 return str(self.value)
7
8+
9+class ReviewStatus(DBEnumeratedType):
10+
11+ PENDING = Item(0, "Pending")
12+ APPROVED = Item(10, "Approved")
13+ DISAPPROVED = Item(100, "Disapproved")
14+
15+ def __str__(self):
16+ return str(self.value)
17+
18+
19 # we have workflow steps known to the lander (lander_service_wrapper). We
20 # also have steps associated in the ticket-system. This provides a mapping
21 # to translate between them
22
23=== modified file 'ticket_system/ticket/api.py'
24--- ticket_system/ticket/api.py 2014-11-05 05:23:20 +0000
25+++ ticket_system/ticket/api.py 2014-11-06 04:57:04 +0000
26@@ -45,6 +45,7 @@
27 get_enum_value,
28 MergeProposal,
29 Review,
30+ ReviewStatus,
31 SubTicket,
32 SourcePackageUpload,
33 SubTicketArtifact,
34@@ -583,6 +584,7 @@
35
36
37 class ReviewResource(ModelResource):
38+
39 ticket = fields.ForeignKey(TicketResource, 'ticket')
40
41 def dehydrate_workflow_step(self, bundle):
42@@ -601,6 +603,21 @@
43 bundle.data['workflow_step'] = value
44 return bundle
45
46+ def dehydrate_status(self, bundle):
47+ return get_enum_title(bundle.data["status"], ReviewStatus)
48+
49+ def hydrate_status(self, bundle):
50+ value = bundle.data.get('status')
51+ if not value:
52+ return bundle
53+ try:
54+ int(value)
55+ except ValueError:
56+ # user passed us "Approved"
57+ value = get_enum_value(value, ReviewStatus)
58+ bundle.data['status'] = value
59+ return bundle
60+
61 class Meta:
62 queryset = Review.objects.all()
63 authorization = Authorization()
64@@ -608,7 +625,7 @@
65 filtering = {
66 'ticket': ALL_WITH_RELATIONS,
67 'review_type': ALL,
68- 'completed': ALL,
69+ 'status': ALL,
70 }
71
72
73
74=== added file 'ticket_system/ticket/migrations/0019_auto__add_field_review_status__chg_field_review_review_type.py'
75--- ticket_system/ticket/migrations/0019_auto__add_field_review_status__chg_field_review_review_type.py 1970-01-01 00:00:00 +0000
76+++ ticket_system/ticket/migrations/0019_auto__add_field_review_status__chg_field_review_review_type.py 2014-11-06 04:57:04 +0000
77@@ -0,0 +1,120 @@
78+# -*- coding: utf-8 -*-
79+import datetime
80+from south.db import db
81+from south.v2 import SchemaMigration
82+from django.db import models
83+
84+
85+class Migration(SchemaMigration):
86+
87+ def forwards(self, orm):
88+ # Adding field 'Review.status'
89+ db.add_column(u'ticket_review', 'status',
90+ self.gf('django.db.models.fields.IntegerField')(default=0),
91+ keep_default=False)
92+
93+
94+ # Changing field 'Review.review_type'
95+ db.alter_column(u'ticket_review', 'review_type', self.gf('django.db.models.fields.CharField')(max_length=254))
96+
97+ def backwards(self, orm):
98+ # Deleting field 'Review.status'
99+ db.delete_column(u'ticket_review', 'status')
100+
101+
102+ # Changing field 'Review.review_type'
103+ db.alter_column(u'ticket_review', 'review_type', self.gf('django.db.models.fields.CharField')(max_length=4096))
104+
105+ models = {
106+ u'project.sourcepackage': {
107+ 'Meta': {'object_name': 'SourcePackage', 'db_table': "'sourcepackage'"},
108+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
109+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '4096'})
110+ },
111+ u'ticket.mergeproposal': {
112+ 'Meta': {'unique_together': "(('subticket', 'lp_url'),)", 'object_name': 'MergeProposal', 'db_table': "'mergeproposal'"},
113+ 'approved_revno': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
114+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
115+ 'lp_url': ('django.db.models.fields.CharField', [], {'max_length': '511'}),
116+ 'source_package_upload': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SourcePackageUpload']", 'null': 'True', 'blank': 'True'}),
117+ 'subticket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'merge_proposals'", 'to': u"orm['ticket.SubTicket']"})
118+ },
119+ u'ticket.review': {
120+ 'Meta': {'object_name': 'Review'},
121+ 'completed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
122+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
123+ 'review_type': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
124+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
125+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"}),
126+ 'workflow_step': ('django.db.models.fields.IntegerField', [], {})
127+ },
128+ u'ticket.sourcepackageupload': {
129+ 'Meta': {'unique_together': "(('subticket', 'version'),)", 'object_name': 'SourcePackageUpload', 'db_table': "'sourcepackageupload'"},
130+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
131+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
132+ 'subticket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source_package_uploads'", 'to': u"orm['ticket.SubTicket']"}),
133+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '254'})
134+ },
135+ u'ticket.subticket': {
136+ 'Meta': {'ordering': "['id']", 'unique_together': "(('ticket', 'sourcepackage'),)", 'object_name': 'SubTicket', 'db_table': "'subticket'"},
137+ 'assignee': ('django.db.models.fields.EmailField', [], {'max_length': '254'}),
138+ 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
139+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
140+ 'sourcepackage': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['project.SourcePackage']"}),
141+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
142+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"})
143+ },
144+ u'ticket.subticketartifact': {
145+ 'Meta': {'object_name': 'SubTicketArtifact', 'db_table': "'subticket_artifact'"},
146+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
147+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
148+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
149+ 'subticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SubTicket']"}),
150+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
151+ },
152+ u'ticket.ticket': {
153+ 'Meta': {'ordering': "['id']", 'object_name': 'Ticket', 'db_table': "'ticket'"},
154+ 'base_image': ('django.db.models.fields.CharField', [], {'default': "'http://cloud-images.ubuntu.com/utopic/current/utopic-server-cloudimg-amd64-disk1.img'", 'max_length': '4096'}),
155+ 'bug_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
156+ 'build_ppa': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'blank': 'True'}),
157+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
158+ 'creator': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'null': 'True', 'blank': 'True'}),
159+ 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
160+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
161+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
162+ 'lander_unit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
163+ 'master_ppa': ('django.db.models.fields.CharField', [], {'default': "'ppa:cprov/ci-master'", 'max_length': '4096'}),
164+ 'owner': ('django.db.models.fields.EmailField', [], {'max_length': '254'}),
165+ 'private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
166+ 'series': ('django.db.models.fields.CharField', [], {'default': "'utopic'", 'max_length': '4096'}),
167+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
168+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
169+ 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
170+ 'uuid': ('django.db.models.fields.CharField', [], {'default': "'37ff8af0-6543-11e4-b341-001c4229f9ac'", 'unique': 'True', 'max_length': '36'}),
171+ 'workflow': ('django.db.models.fields.related.ForeignKey', [], {'default': "'default'", 'to': u"orm['ticket.Workflow']"})
172+ },
173+ u'ticket.ticketartifact': {
174+ 'Meta': {'object_name': 'TicketArtifact', 'db_table': "'ticket_artifact'"},
175+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
176+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
177+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
178+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"}),
179+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
180+ },
181+ u'ticket.ticketcitrainoverlay': {
182+ 'Meta': {'object_name': 'TicketCiTrainOverlay', 'db_table': "'ticketcitrainoverlay'"},
183+ 'comments': ('django.db.models.fields.TextField', [], {}),
184+ 'job_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
185+ 'landers': ('django.db.models.fields.TextField', [], {}),
186+ 'sync_request': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
187+ 'test_notes': ('django.db.models.fields.TextField', [], {}),
188+ 'ticket': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'citrain_overlay'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['ticket.Ticket']"})
189+ },
190+ u'ticket.workflow': {
191+ 'Meta': {'object_name': 'Workflow'},
192+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'primary_key': 'True'}),
193+ 'steps': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
194+ }
195+ }
196+
197+ complete_apps = ['ticket']
198\ No newline at end of file
199
200=== added file 'ticket_system/ticket/migrations/0020_fill_review_status.py'
201--- ticket_system/ticket/migrations/0020_fill_review_status.py 1970-01-01 00:00:00 +0000
202+++ ticket_system/ticket/migrations/0020_fill_review_status.py 2014-11-06 04:57:04 +0000
203@@ -0,0 +1,119 @@
204+# -*- coding: utf-8 -*-
205+import datetime
206+from south.db import db
207+from south.v2 import DataMigration
208+from django.db import models
209+
210+from ci_utils.ticket_states import ReviewStatus
211+
212+
213+class Migration(DataMigration):
214+
215+ def forwards(self, orm):
216+ "Completed reviews become APPROVED."
217+ for r in orm['ticket.Review'].objects.all():
218+ if r.completed:
219+ r.status = ReviewStatus.APPROVED.value
220+ r.save()
221+
222+ def backwards(self, orm):
223+ "APPROVED reviews become 'completed'."
224+ for r in orm['ticket.Review'].objects.all():
225+ if r.status == ReviewStatus.APPROVED.value:
226+ r.completed = True
227+ r.save()
228+
229+ models = {
230+ u'project.sourcepackage': {
231+ 'Meta': {'object_name': 'SourcePackage', 'db_table': "'sourcepackage'"},
232+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
233+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '4096'})
234+ },
235+ u'ticket.mergeproposal': {
236+ 'Meta': {'unique_together': "(('subticket', 'lp_url'),)", 'object_name': 'MergeProposal', 'db_table': "'mergeproposal'"},
237+ 'approved_revno': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
238+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
239+ 'lp_url': ('django.db.models.fields.CharField', [], {'max_length': '511'}),
240+ 'source_package_upload': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SourcePackageUpload']", 'null': 'True', 'blank': 'True'}),
241+ 'subticket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'merge_proposals'", 'to': u"orm['ticket.SubTicket']"})
242+ },
243+ u'ticket.review': {
244+ 'Meta': {'object_name': 'Review'},
245+ 'completed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
246+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
247+ 'review_type': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
248+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
249+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"}),
250+ 'workflow_step': ('django.db.models.fields.IntegerField', [], {})
251+ },
252+ u'ticket.sourcepackageupload': {
253+ 'Meta': {'unique_together': "(('subticket', 'version'),)", 'object_name': 'SourcePackageUpload', 'db_table': "'sourcepackageupload'"},
254+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
255+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
256+ 'subticket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source_package_uploads'", 'to': u"orm['ticket.SubTicket']"}),
257+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '254'})
258+ },
259+ u'ticket.subticket': {
260+ 'Meta': {'ordering': "['id']", 'unique_together': "(('ticket', 'sourcepackage'),)", 'object_name': 'SubTicket', 'db_table': "'subticket'"},
261+ 'assignee': ('django.db.models.fields.EmailField', [], {'max_length': '254'}),
262+ 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
263+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
264+ 'sourcepackage': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['project.SourcePackage']"}),
265+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
266+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"})
267+ },
268+ u'ticket.subticketartifact': {
269+ 'Meta': {'object_name': 'SubTicketArtifact', 'db_table': "'subticket_artifact'"},
270+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
271+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
272+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
273+ 'subticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SubTicket']"}),
274+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
275+ },
276+ u'ticket.ticket': {
277+ 'Meta': {'ordering': "['id']", 'object_name': 'Ticket', 'db_table': "'ticket'"},
278+ 'base_image': ('django.db.models.fields.CharField', [], {'default': "'http://cloud-images.ubuntu.com/utopic/current/utopic-server-cloudimg-amd64-disk1.img'", 'max_length': '4096'}),
279+ 'bug_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
280+ 'build_ppa': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'blank': 'True'}),
281+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
282+ 'creator': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'null': 'True', 'blank': 'True'}),
283+ 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
284+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
285+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
286+ 'lander_unit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
287+ 'master_ppa': ('django.db.models.fields.CharField', [], {'default': "'ppa:cprov/ci-master'", 'max_length': '4096'}),
288+ 'owner': ('django.db.models.fields.EmailField', [], {'max_length': '254'}),
289+ 'private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
290+ 'series': ('django.db.models.fields.CharField', [], {'default': "'utopic'", 'max_length': '4096'}),
291+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
292+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
293+ 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
294+ 'uuid': ('django.db.models.fields.CharField', [], {'default': "'904d7686-6543-11e4-86b0-001c4229f9ac'", 'unique': 'True', 'max_length': '36'}),
295+ 'workflow': ('django.db.models.fields.related.ForeignKey', [], {'default': "'default'", 'to': u"orm['ticket.Workflow']"})
296+ },
297+ u'ticket.ticketartifact': {
298+ 'Meta': {'object_name': 'TicketArtifact', 'db_table': "'ticket_artifact'"},
299+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
300+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
301+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
302+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"}),
303+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
304+ },
305+ u'ticket.ticketcitrainoverlay': {
306+ 'Meta': {'object_name': 'TicketCiTrainOverlay', 'db_table': "'ticketcitrainoverlay'"},
307+ 'comments': ('django.db.models.fields.TextField', [], {}),
308+ 'job_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
309+ 'landers': ('django.db.models.fields.TextField', [], {}),
310+ 'sync_request': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
311+ 'test_notes': ('django.db.models.fields.TextField', [], {}),
312+ 'ticket': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'citrain_overlay'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['ticket.Ticket']"})
313+ },
314+ u'ticket.workflow': {
315+ 'Meta': {'object_name': 'Workflow'},
316+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'primary_key': 'True'}),
317+ 'steps': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
318+ }
319+ }
320+
321+ complete_apps = ['ticket']
322+ symmetrical = True
323
324=== added file 'ticket_system/ticket/migrations/0021_auto__del_field_review_completed.py'
325--- ticket_system/ticket/migrations/0021_auto__del_field_review_completed.py 1970-01-01 00:00:00 +0000
326+++ ticket_system/ticket/migrations/0021_auto__del_field_review_completed.py 2014-11-06 04:57:04 +0000
327@@ -0,0 +1,113 @@
328+# -*- coding: utf-8 -*-
329+import datetime
330+from south.db import db
331+from south.v2 import SchemaMigration
332+from django.db import models
333+
334+
335+class Migration(SchemaMigration):
336+
337+ def forwards(self, orm):
338+ # Deleting field 'Review.completed'
339+ db.delete_column(u'ticket_review', 'completed')
340+
341+
342+ def backwards(self, orm):
343+ # Adding field 'Review.completed'
344+ db.add_column(u'ticket_review', 'completed',
345+ self.gf('django.db.models.fields.BooleanField')(default=False),
346+ keep_default=False)
347+
348+
349+ models = {
350+ u'project.sourcepackage': {
351+ 'Meta': {'object_name': 'SourcePackage', 'db_table': "'sourcepackage'"},
352+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
353+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '4096'})
354+ },
355+ u'ticket.mergeproposal': {
356+ 'Meta': {'unique_together': "(('subticket', 'lp_url'),)", 'object_name': 'MergeProposal', 'db_table': "'mergeproposal'"},
357+ 'approved_revno': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
358+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
359+ 'lp_url': ('django.db.models.fields.CharField', [], {'max_length': '511'}),
360+ 'source_package_upload': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SourcePackageUpload']", 'null': 'True', 'blank': 'True'}),
361+ 'subticket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'merge_proposals'", 'to': u"orm['ticket.SubTicket']"})
362+ },
363+ u'ticket.review': {
364+ 'Meta': {'object_name': 'Review'},
365+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
366+ 'review_type': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
367+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
368+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"}),
369+ 'workflow_step': ('django.db.models.fields.IntegerField', [], {})
370+ },
371+ u'ticket.sourcepackageupload': {
372+ 'Meta': {'unique_together': "(('subticket', 'version'),)", 'object_name': 'SourcePackageUpload', 'db_table': "'sourcepackageupload'"},
373+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
374+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
375+ 'subticket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source_package_uploads'", 'to': u"orm['ticket.SubTicket']"}),
376+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '254'})
377+ },
378+ u'ticket.subticket': {
379+ 'Meta': {'ordering': "['id']", 'unique_together': "(('ticket', 'sourcepackage'),)", 'object_name': 'SubTicket', 'db_table': "'subticket'"},
380+ 'assignee': ('django.db.models.fields.EmailField', [], {'max_length': '254'}),
381+ 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
382+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
383+ 'sourcepackage': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['project.SourcePackage']"}),
384+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
385+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"})
386+ },
387+ u'ticket.subticketartifact': {
388+ 'Meta': {'object_name': 'SubTicketArtifact', 'db_table': "'subticket_artifact'"},
389+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
390+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
391+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
392+ 'subticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SubTicket']"}),
393+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
394+ },
395+ u'ticket.ticket': {
396+ 'Meta': {'ordering': "['id']", 'object_name': 'Ticket', 'db_table': "'ticket'"},
397+ 'base_image': ('django.db.models.fields.CharField', [], {'default': "'http://cloud-images.ubuntu.com/utopic/current/utopic-server-cloudimg-amd64-disk1.img'", 'max_length': '4096'}),
398+ 'bug_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
399+ 'build_ppa': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'blank': 'True'}),
400+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
401+ 'creator': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'null': 'True', 'blank': 'True'}),
402+ 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
403+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
404+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
405+ 'lander_unit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
406+ 'master_ppa': ('django.db.models.fields.CharField', [], {'default': "'ppa:cprov/ci-master'", 'max_length': '4096'}),
407+ 'owner': ('django.db.models.fields.EmailField', [], {'max_length': '254'}),
408+ 'private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
409+ 'series': ('django.db.models.fields.CharField', [], {'default': "'utopic'", 'max_length': '4096'}),
410+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
411+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
412+ 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
413+ 'uuid': ('django.db.models.fields.CharField', [], {'default': "'c434306e-6545-11e4-be5c-001c4229f9ac'", 'unique': 'True', 'max_length': '36'}),
414+ 'workflow': ('django.db.models.fields.related.ForeignKey', [], {'default': "'default'", 'to': u"orm['ticket.Workflow']"})
415+ },
416+ u'ticket.ticketartifact': {
417+ 'Meta': {'object_name': 'TicketArtifact', 'db_table': "'ticket_artifact'"},
418+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
419+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
420+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
421+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"}),
422+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
423+ },
424+ u'ticket.ticketcitrainoverlay': {
425+ 'Meta': {'object_name': 'TicketCiTrainOverlay', 'db_table': "'ticketcitrainoverlay'"},
426+ 'comments': ('django.db.models.fields.TextField', [], {}),
427+ 'job_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
428+ 'landers': ('django.db.models.fields.TextField', [], {}),
429+ 'sync_request': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
430+ 'test_notes': ('django.db.models.fields.TextField', [], {}),
431+ 'ticket': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'citrain_overlay'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['ticket.Ticket']"})
432+ },
433+ u'ticket.workflow': {
434+ 'Meta': {'object_name': 'Workflow'},
435+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'primary_key': 'True'}),
436+ 'steps': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
437+ }
438+ }
439+
440+ complete_apps = ['ticket']
441\ No newline at end of file
442
443=== modified file 'ticket_system/ticket/models.py'
444--- ticket_system/ticket/models.py 2014-11-05 05:23:20 +0000
445+++ ticket_system/ticket/models.py 2014-11-06 04:57:04 +0000
446@@ -29,6 +29,7 @@
447 from ci_utils import unit_config
448
449 from ci_utils.ticket_states import (
450+ ReviewStatus,
451 STEP_TO_WORKFLOW_MAPPING,
452 SubTicketWorkflowStep,
453 SubTicketWorkflowStepStatus,
454@@ -202,7 +203,8 @@
455 def _get_workflow_steps(self):
456 '''build workflow steps injecting pauses where reviews are needed'''
457 steps = json.loads(self.workflow.steps)
458- reviews = self.review_set.filter(completed=False)
459+ reviews = self.review_set.exclude(
460+ status=ReviewStatus.APPROVED.value)
461 pauses = [x.workflow_step for x in reviews]
462 for step in steps:
463 step_val = STEP_TO_WORKFLOW_MAPPING.get(step['name'], -1)
464@@ -390,20 +392,35 @@
465
466
467 class Review(models.Model):
468- ticket = models.ForeignKey(Ticket)
469- workflow_step = models.IntegerField(choices=_choices(TicketWorkflowStep))
470- review_type = models.CharField(max_length=4096)
471- completed = models.BooleanField(default=False)
472+
473+ ticket = models.ForeignKey(
474+ Ticket,
475+ help_text='Context ticket of this review.')
476+
477+ workflow_step = models.IntegerField(
478+ choices=_choices(TicketWorkflowStep),
479+ help_text='Workflow step where the review is required.')
480+
481+ review_type = models.CharField(
482+ max_length=254, null=False, blank=False,
483+ help_text='What type of review is needed.')
484+
485+ status = models.IntegerField(
486+ choices=_choices(ReviewStatus),
487+ default=ReviewStatus.PENDING.value,
488+ help_text='Current review status.')
489
490 def save(self, *args, **kwargs):
491+ """If all reviews are approved, resume the ticket processing."""
492 super(Review, self).save(*args, **kwargs)
493- if self.ticket.review_set.filter(completed=False).count() == 0:
494- # all reviews are complete, if ticket is paused we should
495- # mark it "continue"
496+ all_approved = not self.ticket.review_set.exclude(
497+ status=ReviewStatus.APPROVED.value).exists()
498+ if all_approved:
499 if int(self.ticket.status) == int(TicketWorkflowStepStatus.PAUSED):
500 self.ticket.status = TicketWorkflowStepStatus.CONTINUE
501 self.ticket.save()
502
503 def __unicode__(self):
504- return 'ticket-{} - {}: completed({})'.format(
505- self.ticket.uuid, self.review_type, self.completed)
506+ return '{} - {}: {}'.format(
507+ self.ticket.uuid, self.review_type,
508+ get_enum_title(self.status, ReviewStatus))
509
510=== modified file 'ticket_system/ticket/tests/test_models.py'
511--- ticket_system/ticket/tests/test_models.py 2014-10-14 18:58:42 +0000
512+++ ticket_system/ticket/tests/test_models.py 2014-11-06 04:57:04 +0000
513@@ -24,6 +24,7 @@
514 from ticket.models import (
515 MergeProposal,
516 Review,
517+ ReviewStatus,
518 SourcePackageUpload,
519 SubTicket,
520 SubTicketArtifact,
521@@ -298,13 +299,33 @@
522 self.assertEqual(0, send.call_count)
523
524 # This will trigger the ticket object to be moved to CONTINUE
525- r.completed = True
526+ r.status = ReviewStatus.APPROVED.value
527 r.save()
528 status = Ticket.objects.get(pk=self.ticket.id).status
529 self.assertEqual(TicketWorkflowStepStatus.CONTINUE.value, status)
530 self.assertEqual(1, send.call_count)
531
532
533+class TestReview(TestCase):
534+
535+ def setUp(self):
536+ _m = mock.patch('ticket.models.Ticket.create_container')
537+ self.create_container = _m.start()
538+ self.addCleanup(_m.stop)
539+ self.ticket = create_ticket(owner='test@example.com')
540+
541+ def test_title(self):
542+ # Ensure review title include the ticket UUID, review type and
543+ # its status.
544+ review = Review.objects.create(
545+ ticket=self.ticket, review_type='packaging',
546+ workflow_step=TicketWorkflowStep.PKG_PUBLISHING)
547+
548+ self.assertEqual(
549+ '{} - packaging: Pending'.format(self.ticket.uuid),
550+ unicode(review))
551+
552+
553 class WorkflowTestCase(TestCase):
554 def test_unique_name(self):
555 '''make sure the workflow name is unique'''
556
557=== modified file 'ticket_system/ticket/tests/test_read_api.py'
558--- ticket_system/ticket/tests/test_read_api.py 2014-11-04 09:05:06 +0000
559+++ ticket_system/ticket/tests/test_read_api.py 2014-11-06 04:57:04 +0000
560@@ -161,11 +161,11 @@
561 '/api/v1/subticket/{}/'.format(s.pk)
562 for s in self.ticket.subticket_set.all()],
563 u'reviews': [{
564- u'completed': False,
565 u'id': self.review_1.pk,
566 u'resource_uri': unicode(
567 '/api/v1/review/{0}/'.format(self.review_1.pk)),
568 u'review_type': u'QA',
569+ u'status': u'Pending',
570 u'ticket': unicode(
571 '/api/v1/ticket/{}/'.format(self.ticket.uuid)),
572 u'workflow_step': u'Image testing'}],
573@@ -235,11 +235,11 @@
574 '/api/v1/subticket/{}/'.format(s.pk)
575 for s in self.ticket.subticket_set.all()],
576 u'reviews': [{
577- u'completed': False,
578 u'id': self.review_1.pk,
579 u'resource_uri': unicode(
580 '/api/v1/review/{0}/'.format(self.review_1.pk)),
581 u'review_type': u'QA',
582+ u'status': u'Pending',
583 u'ticket': unicode(
584 '/api/v1/ticket/{}/'.format(self.ticket.uuid)),
585 u'workflow_step': u'Image testing'}],
586@@ -314,11 +314,11 @@
587 '/api/v1/subticket/{}/'.format(s.pk)
588 for s in self.ticket.subticket_set.all()],
589 u'reviews': [{
590- u'completed': False,
591 u'id': self.review_1.pk,
592 u'resource_uri': unicode(
593 '/api/v1/review/{0}/'.format(self.review_1.pk)),
594 u'review_type': u'QA',
595+ u'status': u'Pending',
596 u'ticket': unicode(
597 '/api/v1/ticket/{}/'.format(self.ticket.uuid)),
598 u'workflow_step': u'Image testing'}],
599@@ -439,11 +439,11 @@
600 u'/api/v1/subticket/{}/'.format(s.pk)
601 for s in self.ticket.subticket_set.all()],
602 u'reviews': [{
603- u'completed': False,
604 u'id': self.review_1.pk,
605 u'resource_uri': unicode(
606 '/api/v1/review/{0}/'.format(self.review_1.pk)),
607 u'review_type': u'QA',
608+ u'status': u'Pending',
609 u'ticket': unicode(
610 '/api/v1/ticket/{0}/'.format(self.ticket.uuid)),
611 u'workflow_step': u'Image testing'}],
612
613=== modified file 'ticket_system/ticket/tests/test_write_api.py'
614--- ticket_system/ticket/tests/test_write_api.py 2014-11-05 05:23:20 +0000
615+++ ticket_system/ticket/tests/test_write_api.py 2014-11-06 04:57:04 +0000
616@@ -30,6 +30,7 @@
617 m.start()
618 from ticket.models import (
619 Review,
620+ ReviewStatus,
621 SourcePackageUpload,
622 SubTicket,
623 SubTicketWorkflowStep,
624@@ -168,11 +169,11 @@
625 self.assertEqual('QA', review_pkg.review_type)
626 self.assertEqual(
627 TicketWorkflowStep.PKG_VALIDATION.value, review_pkg.workflow_step)
628- self.assertFalse(review_pkg.completed)
629+ self.assertEqual(ReviewStatus.PENDING.value, review_pkg.status)
630 self.assertEqual('QA', review_img.review_type)
631 self.assertEqual(
632 TicketWorkflowStep.IMAGE_BUILDING.value, review_img.workflow_step)
633- self.assertFalse(review_img.completed)
634+ self.assertFalse(ReviewStatus.PENDING.value, review_img.status)
635
636 # Note that the reviews created via POST are new, they are not
637 # mysteriously reused/updated by tastypie.
638@@ -189,7 +190,7 @@
639 'title': 'Atomic Patched Ticket',
640 'reviews': [{
641 'id': review.id,
642- 'completed': True,
643+ 'status': 'Approved',
644 'workflow_step': 'Image building',
645 }],
646 })
647@@ -200,7 +201,7 @@
648 self.assertEqual('Atomic Patched Ticket', unicode(ticket))
649 [new_review] = list(ticket.review_set.all())
650 self.assertEqual(review.id, new_review.id)
651- self.assertTrue(new_review.completed)
652+ self.assertEqual(ReviewStatus.APPROVED.value, new_review.status)
653
654 def test_post_ticket_atomically_reuses_workflows(self):
655 # `Ticket` atomic creation re-uses existing `Workflows`s.
656@@ -721,10 +722,12 @@
657 workflow_step=0)
658 uri = 'review/' + str(t.pk) + '/'
659 params = self.get(uri)
660- self.assertFalse(params['completed'])
661- params['completed'] = True
662+ self.assertEqual(ReviewStatus.PENDING.title, params['status'])
663+ params['status'] = 'Approved'
664 self.patch(uri, params)
665- self.assertTrue(Review.objects.get(pk=t.id).completed)
666+ self.assertEqual(
667+ ReviewStatus.APPROVED.value,
668+ Review.objects.get(pk=t.id).status)
669
670 def test_delete(self):
671 t = Review.objects.create(ticket=self.ticket, review_type='a',
672@@ -739,5 +742,5 @@
673 resp = self.get('review/', {'ticket': self.ticket.pk})
674 self.assertEqual(1, resp['meta']['total_count'])
675
676- resp = self.get('review/', {'completed': True})
677+ resp = self.get('review/', {'status': '10'})
678 self.assertEqual(0, resp['meta']['total_count'])

Subscribers

People subscribed via source and target branches