Merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.5 into lp:ubuntuone-control-panel/stable-3-0

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 251
Merged at revision: 250
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.5
Merge into: lp:ubuntuone-control-panel/stable-3-0
Diff against target: 508 lines (+248/-40)
11 files modified
data/qt/filesyncstatus.ui (+9/-2)
data/qt/images.qrc (+1/-0)
ubuntuone/controlpanel/gui/__init__.py (+17/-4)
ubuntuone/controlpanel/gui/qt/controlpanel.py (+9/-20)
ubuntuone/controlpanel/gui/qt/filesyncstatus.py (+12/-3)
ubuntuone/controlpanel/gui/qt/gui.py (+7/-5)
ubuntuone/controlpanel/gui/qt/tests/__init__.py (+67/-0)
ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py (+2/-4)
ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py (+29/-1)
ubuntuone/controlpanel/gui/qt/tests/test_start.py (+20/-1)
ubuntuone/controlpanel/gui/tests/test_show_quota_warning.py (+75/-0)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.5
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Review via email: mp+94067@code.launchpad.net

Commit message

- Updating from trunk up to revno 262:

[ Diego Sarmentero <email address hidden> ]
  - Control Panel shows quota in red with more accuracy depending on the
    free space (LP: #901314).
  - The QT UI will make a button inactive while a (re-) connection is
    in progress (LP: #878867).

[ Natalia B. Bidart <email address hidden> ]
  - Center the main window when openning it (LP: #934173).

To post a comment you must log in.
251. By Natalia Bidart

Attaching bug numbers.

Revision history for this message
Roberto Alsina (ralsina) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/qt/filesyncstatus.ui'
2--- data/qt/filesyncstatus.ui 2011-08-23 16:03:31 +0000
3+++ data/qt/filesyncstatus.ui 2012-02-21 23:16:17 +0000
4@@ -7,7 +7,7 @@
5 <x>0</x>
6 <y>0</y>
7 <width>94</width>
8- <height>49</height>
9+ <height>52</height>
10 </rect>
11 </property>
12 <property name="windowTitle">
13@@ -20,7 +20,14 @@
14 <item>
15 <layout class="QHBoxLayout" name="horizontalLayout">
16 <item>
17- <widget class="QLabel" name="sync_status_icon"/>
18+ <widget class="QLabel" name="sync_status_icon">
19+ <property name="sizePolicy">
20+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
21+ <horstretch>0</horstretch>
22+ <verstretch>0</verstretch>
23+ </sizepolicy>
24+ </property>
25+ </widget>
26 </item>
27 <item>
28 <widget class="QLabel" name="sync_status_label">
29
30=== modified file 'data/qt/images.qrc'
31--- data/qt/images.qrc 2011-09-06 13:43:25 +0000
32+++ data/qt/images.qrc 2012-02-21 23:16:17 +0000
33@@ -6,6 +6,7 @@
34 <file>../sync_status_alert.png</file>
35 <file>../sync_status_sync_done.png</file>
36 <file>../sync_status_syncing.png</file>
37+ <file>../sync_status_loading.png</file>
38 <file>../folder.png</file>
39 <file>../computer.png</file>
40 <file>../phone.png</file>
41
42=== added file 'data/sync_status_loading.png'
43Binary files data/sync_status_loading.png 1970-01-01 00:00:00 +0000 and data/sync_status_loading.png 2012-02-21 23:16:17 +0000 differ
44=== modified file 'ubuntuone/controlpanel/gui/__init__.py'
45--- ubuntuone/controlpanel/gui/__init__.py 2012-02-06 15:23:27 +0000
46+++ ubuntuone/controlpanel/gui/__init__.py 2012-02-21 23:16:17 +0000
47@@ -1,9 +1,6 @@
48 # -*- coding: utf-8 -*-
49
50-# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
51-# Authors: Alejandro J. Cura <alecu@canonical.com>
52-#
53-# Copyright 2011 Canonical Ltd.
54+# Copyright 2011-2012 Canonical Ltd.
55 #
56 # This program is free software: you can redistribute it and/or modify it
57 # under the terms of the GNU General Public License version 3, as published
58@@ -29,10 +26,14 @@
59
60 ERROR_COLOR = u'red'
61 KILOBYTES = 1024
62+FREE_ACCOUNT_SIZE = (KILOBYTES ** 3) * 5 # 5 gigs
63 NO_OP = lambda *a, **kw: None
64 # http://design.canonical.com/the-toolkit/ubuntu-logo-and-circle-of-friends/
65 ORANGE = u'#DD4814'
66 QUOTA_THRESHOLD = 0.95
67+QUOTA_THRESHOLD_ACCOUNTS = {
68+ 'free': FREE_ACCOUNT_SIZE * 0.1, # 10% of the free account size
69+ 'pay': (KILOBYTES ** 3) * 3} # 3 gigs
70 SHARES_MIN_SIZE_FULL = 1048576
71 SUCCESS_COLOR = u'green'
72
73@@ -153,6 +154,7 @@
74 NO_FOLDERS = _('No folders to show.')
75 NO_PAIRING_RECORD = _('There is no Ubuntu One pairing record.')
76 PERCENTAGE_LABEL = _('%(percentage)s used')
77+PLEASE_WAIT = _('Please wait')
78 QUOTA_LABEL = _('Using %(used)s of %(total)s (%(percentage).0f%%)')
79 USAGE_LABEL = _('%(used)s of %(total)s')
80 SERVICES_BUTTON_TOOLTIP = _('Manage the sync services')
81@@ -184,3 +186,14 @@
82 str_bytes = str_bytes.rstrip('0')
83 str_bytes = str_bytes.rstrip('.')
84 return '%s %s' % (str_bytes, units[unit])
85+
86+
87+def show_quota_warning(int_bytes_used, int_bytes_total):
88+ """Return True if the user should be warn about the remaining quota."""
89+ available = (int_bytes_total - int_bytes_used)
90+ free_threshold = (int_bytes_total == FREE_ACCOUNT_SIZE and
91+ available <= QUOTA_THRESHOLD_ACCOUNTS['free'])
92+ payed_threshold = (int_bytes_total > FREE_ACCOUNT_SIZE and
93+ available <= QUOTA_THRESHOLD_ACCOUNTS['pay'])
94+
95+ return free_threshold or payed_threshold
96
97=== modified file 'ubuntuone/controlpanel/gui/qt/controlpanel.py'
98--- ubuntuone/controlpanel/gui/qt/controlpanel.py 2011-09-23 15:05:27 +0000
99+++ ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-02-21 23:16:17 +0000
100@@ -1,9 +1,6 @@
101 # -*- coding: utf-8 -*-
102
103-# Authors: Alejandro J. Cura <alecu@canonical.com>
104-# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
105-#
106-# Copyright 2011 Canonical Ltd.
107+# Copyright 2012 Canonical Ltd.
108 #
109 # This program is free software: you can redistribute it and/or modify it
110 # under the terms of the GNU General Public License version 3, as published
111@@ -28,12 +25,12 @@
112 from ubuntuone.controlpanel.logger import setup_logging, log_call
113 from ubuntuone.controlpanel.gui import (
114 humanize,
115+ show_quota_warning,
116 EDIT_SERVICES_LINK,
117 FACEBOOK_LINK,
118 GET_SUPPORT_LINK,
119 GREETING,
120 PERCENTAGE_LABEL,
121- QUOTA_THRESHOLD,
122 TWITTER_LINK,
123 USAGE_LABEL,
124 )
125@@ -105,27 +102,19 @@
126 total = int(info['quota_total'])
127 percentage_value = ((used / total) * 100)
128 percentage = {'percentage': PERCENTAGE_STYLE % percentage_value}
129+ show_warning = show_quota_warning(used, total)
130 data = {'used': humanize(used), 'total': humanize(total)}
131 self.ui.percentage_usage_label.setText(PERCENTAGE_LABEL % percentage)
132 self.ui.quota_usage_label.setText(USAGE_LABEL % data)
133- self._update_quota({'percentage': percentage_value})
134+ self._update_quota(show_warning)
135
136 @log_call(logger.debug)
137- def _update_quota(self, data=None):
138+ def _update_quota(self, show_warning=False):
139 """Update the quota info."""
140- fraction = 0.0
141- if data is not None:
142- fraction = data.get('percentage', 0.0) / 100
143- if fraction > 0 and fraction < 0.05:
144- fraction = 0.05
145- else:
146- fraction = round(fraction, 2)
147-
148- logger.debug('ManagementPanel: updating quota to %r.', fraction)
149- self.ui.percentage_usage_label.setProperty("OverQuota",
150- fraction >= QUOTA_THRESHOLD)
151- self.ui.quota_usage_label.setProperty("OverQuota",
152- fraction >= QUOTA_THRESHOLD)
153+ logger.debug('ManagementPanel: show warning in quota %r.',
154+ show_warning)
155+ self.ui.percentage_usage_label.setProperty("OverQuota", show_warning)
156+ self.ui.quota_usage_label.setProperty("OverQuota", show_warning)
157 self.ui.percentage_usage_label.style().unpolish(
158 self.ui.percentage_usage_label)
159 self.ui.percentage_usage_label.style().polish(
160
161=== modified file 'ubuntuone/controlpanel/gui/qt/filesyncstatus.py'
162--- ubuntuone/controlpanel/gui/qt/filesyncstatus.py 2011-09-02 17:59:39 +0000
163+++ ubuntuone/controlpanel/gui/qt/filesyncstatus.py 2012-02-21 23:16:17 +0000
164@@ -1,8 +1,6 @@
165 # -*- coding: utf-8 -*-
166-
167-# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
168 #
169-# Copyright 2011 Canonical Ltd.
170+# Copyright 2011-2012 Canonical Ltd.
171 #
172 # This program is free software: you can redistribute it and/or modify it
173 # under the terms of the GNU General Public License version 3, as published
174@@ -44,6 +42,8 @@
175 FILE_SYNC_STOP_TOOLTIP,
176 FILE_SYNC_STOPPED,
177 FILE_SYNC_SYNCING,
178+ LOADING,
179+ PLEASE_WAIT,
180 )
181 from ubuntuone.controlpanel.gui.qt import pixmap_from_name
182 from ubuntuone.controlpanel.gui.qt.ui import filesyncstatus_ui
183@@ -186,6 +186,15 @@
184 """Button was clicked, act accordingly to the label."""
185 self.ui.sync_status_button.setEnabled(False)
186 if self._backend_method is not None:
187+ self.ui.sync_status_label.setText(LOADING)
188+ self.ui.sync_status_button.setText(PLEASE_WAIT)
189+ pixmap = pixmap_from_name('sync_status_loading')
190+ self.ui.sync_status_icon.setPixmap(pixmap)
191+ self.ui.sync_status_button.setProperty("secondary", True)
192+ self.ui.sync_status_button.style().unpolish(
193+ self.ui.sync_status_button)
194+ self.ui.sync_status_button.style().polish(
195+ self.ui.sync_status_button)
196 self._backend_method()
197 else:
198 logger.error('on_sync_status_button_clicked: backend method is '
199
200=== modified file 'ubuntuone/controlpanel/gui/qt/gui.py'
201--- ubuntuone/controlpanel/gui/qt/gui.py 2012-02-07 13:54:46 +0000
202+++ ubuntuone/controlpanel/gui/qt/gui.py 2012-02-21 23:16:17 +0000
203@@ -1,8 +1,6 @@
204 # -*- coding: utf-8 -*-
205-
206-# Authors: Alejandro J. Cura <alecu@canonical.com>
207 #
208-# Copyright 2011 Canonical Ltd.
209+# Copyright 2011-2012 Canonical Ltd.
210 #
211 # This program is free software: you can redistribute it and/or modify it
212 # under the terms of the GNU General Public License version 3, as published
213@@ -18,8 +16,7 @@
214
215 """The user interface for the control panel for Ubuntu One."""
216
217-
218-from PyQt4 import QtGui
219+from PyQt4 import QtGui, QtCore
220
221 from ubuntuone.controlpanel.gui.qt.systray import TrayIcon
222 from ubuntuone.controlpanel.gui.qt.ui import mainwindow_ui
223@@ -60,6 +57,11 @@
224 window = MainWindow()
225 else:
226 window = MainWindow(close_callback=close_callback)
227+ app = QtGui.QApplication.instance()
228+ style = QtGui.QStyle.alignedRect(
229+ QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter,
230+ window.size(), app.desktop().availableGeometry())
231+ window.setGeometry(style)
232 window.show()
233 else:
234 window = None
235
236=== modified file 'ubuntuone/controlpanel/gui/qt/tests/__init__.py'
237--- ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-02-06 15:23:27 +0000
238+++ ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-02-21 23:16:17 +0000
239@@ -233,6 +233,73 @@
240 # pylint: enable=C0103
241
242
243+class FakePageUiStyle(object):
244+ """Fake the page."""
245+
246+ def __init__(self):
247+ self.ui = self
248+ self.properties = {}
249+ super(FakePageUiStyle, self).__init__()
250+
251+ def wizard(self):
252+ """Use itself as a fake wizard, too."""
253+ return self
254+
255+ def text(self):
256+ """Return text."""
257+ return self.properties.get('text', '')
258+
259+ # setText, setEnabled are inherited
260+ # pylint: disable=C0103
261+ def setText(self, text):
262+ """Save text."""
263+ self.properties['text'] = text
264+
265+ def setEnabled(self, value):
266+ """Fake setEnabled."""
267+ self.properties['enabled'] = value
268+
269+ def enabled(self):
270+ """Fake enabled."""
271+ return self.properties.get('enabled', False)
272+
273+ def isEnabled(self):
274+ """Fake isEnabled."""
275+ return self.properties.get('enabled', False)
276+
277+ def setProperty(self, key, val):
278+ """Fake setProperty to restyle some widget."""
279+ self.properties[key] = val
280+
281+ def property(self, key):
282+ """Fake property from widget style."""
283+ return self.properties.get(key, False)
284+
285+ def setDefault(self, val):
286+ """Fake button setDefault."""
287+ self.properties['default'] = val
288+
289+ def style(self):
290+ """Fake style."""
291+ return self
292+
293+ def unpolish(self, *args):
294+ """Fake unpolish."""
295+ self.properties['unpolish'] = len(args) > 0
296+
297+ def polish(self, *args):
298+ """Fake polish."""
299+ self.properties['polish'] = len(args) > 0
300+
301+ def setVisible(self, val):
302+ """Fake setVisible from Qt."""
303+ self.properties['visible'] = val
304+
305+ def isVisible(self):
306+ """Fake isVisible from Qt."""
307+ return self.properties['visible']
308+
309+
310 class BaseTestCase(TestCase):
311 """Base Test Case."""
312
313
314=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py'
315--- ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py 2011-09-12 20:17:37 +0000
316+++ ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py 2012-02-21 23:16:17 +0000
317@@ -126,9 +126,8 @@
318
319 def test_update_over_quota(self):
320 """Check the labels state when the user exceed the quota."""
321- percentage_value = 100
322 # pylint: disable=W0212
323- self.ui._update_quota({'percentage': percentage_value})
324+ self.ui._update_quota(True)
325 # pylint: enable=W0212
326
327 self.assertTrue(
328@@ -138,9 +137,8 @@
329
330 def test_update_quota_in_range(self):
331 """Check the labels state when the quota is under the threshold."""
332- percentage_value = 50
333 # pylint: disable=W0212
334- self.ui._update_quota({'percentage': percentage_value})
335+ self.ui._update_quota(False)
336 # pylint: enable=W0212
337
338 self.assertFalse(
339
340=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py'
341--- ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2011-10-28 08:19:20 +0000
342+++ ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2012-02-21 23:16:17 +0000
343@@ -21,7 +21,14 @@
344 from twisted.internet import defer
345
346 from ubuntuone.controlpanel.gui.qt import filesyncstatus as gui
347-from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase
348+from ubuntuone.controlpanel.gui.qt.tests import (
349+ BaseTestCase,
350+ FakePageUiStyle,
351+)
352+from ubuntuone.controlpanel.gui import (
353+ LOADING,
354+ PLEASE_WAIT,
355+)
356
357 backend = gui.backend # pylint: disable=C0103
358
359@@ -171,3 +178,24 @@
360 action=gui.FILE_SYNC_RESTART,
361 callback='restart_files',
362 tooltip=gui.FILE_SYNC_RESTART_TOOLTIP)
363+
364+ def test_on_sync_status_button_clicked(self):
365+ """Check the labels and icon when the button is pressed."""
366+ # Ensure the _backend_method is not None to execute the first
367+ # part of the If.
368+ self.ui._backend_method = lambda: 'Not None'
369+ self.patch(self.ui.ui, "sync_status_button", FakePageUiStyle())
370+ # Simulate the click event
371+ self.ui.on_sync_status_button_clicked()
372+ self.assertFalse(self.ui.ui.sync_status_button.isEnabled())
373+ self.assertTrue(self.ui.ui.sync_status_button.property("secondary"))
374+ actual_text = self.ui.ui.sync_status_label.text()
375+ self.assertEqual(actual_text, LOADING)
376+ self.assertEqual(self.ui.ui.sync_status_button.text(), PLEASE_WAIT)
377+ actual_icon = self.ui.ui.sync_status_icon.pixmap()
378+ expected_icon = gui.pixmap_from_name('sync_status_loading')
379+ self.assertEqualPixmaps(expected_icon, actual_icon)
380+ self.assertTrue(
381+ self.ui.ui.sync_status_button.properties.get('unpolish', True))
382+ self.assertTrue(
383+ self.ui.ui.sync_status_button.properties.get('polish', True))
384
385=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_start.py'
386--- ubuntuone/controlpanel/gui/qt/tests/test_start.py 2012-02-07 13:54:46 +0000
387+++ ubuntuone/controlpanel/gui/qt/tests/test_start.py 2012-02-21 23:16:17 +0000
388@@ -28,9 +28,12 @@
389
390 """A fake thing."""
391
392+ shown = False
393+ size = lambda *a: gui.QtCore.QSize(123456, 654321)
394+ style = None
395+
396 def __init__(self):
397 self.args = []
398- self.shown = False
399
400 def __call__(self, *args, **kwargs):
401 self.args.append((args, kwargs))
402@@ -40,6 +43,13 @@
403 """Show."""
404 self.shown = True
405
406+ # Invalid name "setGeometry"
407+ # pylint: disable=C0103
408+
409+ def setGeometry(self, style):
410+ """Save the new geometry."""
411+ self.style = style
412+
413
414 class StartTestCase(TestCase):
415 """Test the qt control panel."""
416@@ -87,3 +97,12 @@
417 kwargs = {'close_callback': self.close_cb, 'window': None}
418 self.assertEqual(self.tray_icon.args, [((), kwargs)])
419 self.assertEqual(self.main_window.args, [])
420+
421+ def test_center_window(self):
422+ """The main window should be centered."""
423+ gui.start(close_callback=self.close_cb)
424+ app = gui.QtGui.QApplication.instance()
425+ expected = gui.QtGui.QStyle.alignedRect(gui.QtCore.Qt.LeftToRight,
426+ gui.QtCore.Qt.AlignCenter, self.main_window.size(),
427+ app.desktop().availableGeometry())
428+ self.assertEqual(self.main_window.style, expected)
429
430=== added file 'ubuntuone/controlpanel/gui/tests/test_show_quota_warning.py'
431--- ubuntuone/controlpanel/gui/tests/test_show_quota_warning.py 1970-01-01 00:00:00 +0000
432+++ ubuntuone/controlpanel/gui/tests/test_show_quota_warning.py 2012-02-21 23:16:17 +0000
433@@ -0,0 +1,75 @@
434+# -*- coding: utf-8 -*-
435+
436+# Copyright 2012 Canonical Ltd.
437+#
438+# This program is free software: you can redistribute it and/or modify it
439+# under the terms of the GNU General Public License version 3, as published
440+# by the Free Software Foundation.
441+#
442+# This program is distributed in the hope that it will be useful, but
443+# WITHOUT ANY WARRANTY; without even the implied warranties of
444+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
445+# PURPOSE. See the GNU General Public License for more details.
446+#
447+# You should have received a copy of the GNU General Public License along
448+# with this program. If not, see <http://www.gnu.org/licenses/>.
449+
450+"""Tests for the show_quota_warning function."""
451+
452+from ubuntuone.controlpanel.tests import TestCase
453+from ubuntuone.controlpanel.gui import show_quota_warning
454+
455+
456+GB5 = 5368709120 # 5 Gb
457+GB25 = 26843545600 # 25 Gb
458+
459+
460+class QuotaWarningTestCase(TestCase):
461+
462+ """Test cases for the humanize function."""
463+
464+ def test_quota_warning_with_used_0_free_account(self):
465+ """Check the return value for show_quota_warning with 0 bytes used."""
466+ self.assertFalse(show_quota_warning(0, GB5))
467+
468+ def test_quota_warning_with_free_account_used_medium(self):
469+ """Check the return value for show_quota_warning with 2.5 gb used."""
470+ used = 2.5 * (1024 ** 3) # 2.5 Gb
471+ self.assertFalse(show_quota_warning(used, GB5))
472+
473+ def test_quota_warning_with_free_account_used_almost_full(self):
474+ """Check the return value for show_quota_warning with 4.5 gb used."""
475+ # For free accounts the warning should be activate when the user
476+ # has equal or less than 500 mb remaining
477+ used = 4.5 * (1024 ** 3) # 4.5 Gb
478+ self.assertTrue(show_quota_warning(used, GB5))
479+
480+ def test_quota_warning_with_free_account_used_full(self):
481+ """Check the return value for show_quota_warning with 5 gb used."""
482+ # For free accounts the warning should be activate when the user
483+ # has equal or less than 500 mb remaining
484+ used = 5.0 * (1024 ** 3) # 5.0 Gb
485+ self.assertTrue(show_quota_warning(used, GB5))
486+
487+ def test_quota_warning_with_used_0_pay_account(self):
488+ """Check the return value for show_quota_warning with 0 bytes used."""
489+ self.assertFalse(show_quota_warning(0, GB25))
490+
491+ def test_quota_warning_with_pay_account_used_medium(self):
492+ """Check the return value for show_quota_warning with 12.5 gb used."""
493+ used = 12.5 * (1024 ** 3) # 12.5 Gb
494+ self.assertFalse(show_quota_warning(used, GB25))
495+
496+ def test_quota_warning_with_pay_account_used_almost_full(self):
497+ """Check the return value for show_quota_warning with 22 gb used."""
498+ # For pay accounts the warning should be activate when the user
499+ # has equal or less than 3 Gb remaining
500+ used = 22 * (1024 ** 3) # 22 Gb
501+ self.assertTrue(show_quota_warning(used, GB25))
502+
503+ def test_quota_warning_with_pay_account_used_full(self):
504+ """Check the return value for show_quota_warning with 25 gb used."""
505+ # For free accounts the warning should be activate when the user
506+ # has equal or less than 3 gb remaining
507+ used = 25 * (1024 ** 3) # 25 Gb
508+ self.assertTrue(show_quota_warning(used, GB25))

Subscribers

People subscribed via source and target branches