def testOneNew(self): """One accepted signoff on the new appversion, none on the old. Old appversion comes back empty. """ locale = Locale.objects.get(code='da') repo = self._setup(locale, None, Action.ACCEPTED) eq_(repo.changesets.count(), 2) eq_(_actions4appversion(self.old_av, set([locale.id]), 100), ({}, set([locale.id]))) a4av, not_found = _actions4appversion(self.new_av, set([locale.id]), 100) eq_(not_found, set()) eq_(a4av.keys(), [locale.id]) flag, action_id = a4av[locale.id].items()[0] eq_(flag, Action.ACCEPTED) eq_(Signoff.objects.get(action=action_id).locale_id, locale.id) flagdata = flags4appversions() ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], {'da': ['fx1.1', { Action.ACCEPTED: self.actions[1].id }]}) eq_(flagdata[self.old_av], {})
def testOneOld(self): """One locale signed off and accepted on old appversion, nothing new on new, thus falling back to the old one. """ locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.ACCEPTED, None) eq_(repo.changesets.count(), 2) flaglocs4av, not_found = _actions4appversion(self.old_av, set([locale.id]), 100) eq_(not_found, set()) eq_(flaglocs4av.keys(), [locale.id]) flag, action_id = flaglocs4av[locale.id].items()[0] eq_(flag, Action.ACCEPTED) eq_(Signoff.objects.get(action=action_id).locale_id, locale.id) eq_(_actions4appversion(self.new_av, set([locale.id]), 100), ({}, set([locale.id]))) flagdata = flags4appversions() ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], {'da': ['fx1.0', { Action.ACCEPTED: self.actions[1].id }]}) eq_(flagdata[self.old_av], flagdata[self.new_av])
def _get_flags_and_actions(self): __, flags = (api.flags4appversions([self.av], locales=[self.locale.id]) .get(self.av, {}) .get(self.locale.code, [None, {}])) actions = Action.objects.filter(id__in=flags.values()) return flags, actions
def testOneOld(self): """One locale signed off and accepted on old appversion, nothing new on new, thus falling back to the old one. """ locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.ACCEPTED, None) self.assertEqual(repo.changesets.count(), 2) flaglocs4av, not_found = _actions4appversion(self.old_av, {locale.id}, None, 100) self.assertEqual(not_found, set()) self.assertListEqual(flaglocs4av.keys(), [locale.id]) flag, action_id = flaglocs4av[locale.id].items()[0] self.assertEqual(flag, Action.ACCEPTED) self.assertEqual( Signoff.objects.get(action=action_id).locale_id, locale.id) self.assertTupleEqual( _actions4appversion(self.new_av, {locale.id}, None, 100), ({}, {locale.id})) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual( flagdata[self.new_av], {'da': ['fx1.0', { Action.ACCEPTED: self.actions[1].id }]}) self.assertDictEqual(flagdata[self.old_av], flagdata[self.new_av])
def confirm_ship_mstone(request): """Intermediate page when shipping a milestone. Gathers all data to verify when shipping. Ends up in ship_mstone if everything is fine. Redirects to milestones() in case of trouble. """ if not request.GET.get('ms'): raise http.Http404("ms must be supplied") mstone = get_object_or_404(Milestone, code=request.GET['ms']) if mstone.status != Milestone.OPEN: return http.HttpResponseRedirect(reverse('shipping.views.milestones')) flags4loc = (flags4appversions(appversions={'id': mstone.appver.id}) [mstone.appver]) pending_locs = [] good = 0 for loc, (real_av, flags) in flags4loc.iteritems(): if real_av == mstone.appver.code and Action.PENDING in flags: # pending pending_locs.append(loc) if Action.ACCEPTED in flags: # good good += 1 pending_locs.sort() return render(request, 'shipping/confirm-ship.html', { 'mstone': mstone, 'pending_locs': pending_locs, 'good': good, 'login_form_needs_reload': True, 'request': request, })
def handle(self, *args, **options): locflags4av = flags4appversions() actions = set() for flags4loc in locflags4av.itervalues(): for real_av, flags in flags4loc.itervalues(): if Action.ACCEPTED in flags: actions.add(flags[Action.ACCEPTED]) push4action = dict( Action.objects.filter(id__in=actions).values_list( 'id', 'signoff__push')) cs4push = dict( Push.objects.filter(id__in=set(push4action.values())).annotate( tip=Max("changesets")).values_list('id', 'tip')) revs = dict( Changeset.objects.filter(id__in=cs4push.values()).values_list( 'id', 'revision')) rv = defaultdict(dict) for av, flags4loc in locflags4av.iteritems(): for loc, (real_av, flags) in flags4loc.iteritems(): if Action.ACCEPTED not in flags: continue pushid = push4action[flags[Action.ACCEPTED]] rv[av.code][loc] = revs[cs4push[pushid]][:12] print json.dumps(rv, indent=2, sort_keys=True)
def get_context_data(self, lang, appver): # which pushes to show real_av, flags = (flags4appversions([appver], locales=[lang.id]).get( appver, {}).get(lang.code, [None, {}])) actions = list( Action.objects.filter(id__in=flags.values()).select_related( 'signoff__push__repository', 'author')) # get current status of signoffs push4action = dict((a.id, a.signoff.push) for a in actions) pending = push4action.get(flags.get(Action.PENDING)) rejected = push4action.get(flags.get(Action.REJECTED)) accepted = push4action.get(flags.get(Action.ACCEPTED)) if real_av != appver.code and accepted is not None: # we're falling back, add the accepted push to the table fallback = accepted else: fallback = None pushes_data = self.annotated_pushes( lang, appver, actions=actions, flags=flags, fallback=fallback, count=self.count, ) # Check if this is the very first release. # Only applies to products that have a fallback (Firefox for example). first = appver.fallback is not None and accepted is None try: team_locale = (TeamLocaleThrough.objects.current().get( locale=lang).team) except TeamLocaleThrough.DoesNotExist: team_locale = lang if pushes_data['next_push_date']: next_push_date = pushes_data['next_push_date'].isoformat() else: next_push_date = None return { 'appver': appver, 'language': lang, 'team_locale': team_locale, 'pushes': pushes_data['pushes'], 'pushes_left': pushes_data['pushes_left'], 'next_push_date': next_push_date, 'pending': pending, 'rejected': rejected, 'accepted': accepted, 'first': first, 'suggested_signoff': pushes_data['suggested_signoff'], 'login_form_needs_reload': True, 'fallback': fallback, 'real_av': real_av, }
def testOneNew(self): """One accepted signoff on the new appversion, none on the old. Old appversion comes back empty. """ locale = Locale.objects.get(code='da') repo = self._setup(locale, None, Action.ACCEPTED) self.assertEqual(repo.changesets.count(), 2) self.assertTupleEqual( _actions4appversion(self.old_av, {locale.id}, None, 100), ({}, {locale.id})) a4av, not_found = _actions4appversion(self.new_av, {locale.id}, None, 100) self.assertEqual(not_found, set()) self.assertListEqual(list(a4av.keys()), [locale.id]) flag, action_id = list(a4av[locale.id].items())[0] self.assertEqual(flag, Action.ACCEPTED) self.assertEqual( Signoff.objects.get(action=action_id).locale_id, locale.id) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual( flagdata[self.new_av], {'da': ['fx1.1', {Action.ACCEPTED: self.actions[1].id}]}) self.assertDictEqual(flagdata[self.old_av], {})
def testOneNew(self): """One accepted signoff on the new appversion, none on the old. Old appversion comes back empty. """ locale = Locale.objects.get(code='da') repo = self._setup(locale, None, Action.ACCEPTED) self.assertEqual(repo.changesets.count(), 2) self.assertTupleEqual( _actions4appversion(self.old_av, {locale.id}, None, 100), ({}, {locale.id})) a4av, not_found = _actions4appversion(self.new_av, {locale.id}, None, 100) self.assertEqual(not_found, set()) self.assertListEqual(a4av.keys(), [locale.id]) flag, action_id = a4av[locale.id].items()[0] self.assertEqual(flag, Action.ACCEPTED) self.assertEqual( Signoff.objects.get(action=action_id).locale_id, locale.id) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual( flagdata[self.new_av], {'da': ['fx1.1', { Action.ACCEPTED: self.actions[1].id }]}) self.assertDictEqual(flagdata[self.old_av], {})
def test_signoff_annotated_pushes(self): view = SignoffView() locale = Locale.objects.get(code='de') real_av, flags = (api.flags4appversions( locales={'id': locale.id}, appversions={'id': self.av.id}) .get(self.av, {}) .get(locale.code, [None, {}])) actions = list(Action.objects.filter(id__in=flags.values()) .select_related('signoff__push__repository', 'author')) fallback, = actions assert fallback.flag == Action.ACCEPTED, fallback.flag pushes, suggested_signoff = view.annotated_pushes( actions, flags, fallback, locale, self.av ) eq_(suggested_signoff, None) changesets = [c for p in pushes for c in p['changes']] revisions = [x.revision for x in changesets] # only `de` changes in the right order eq_(revisions, [u'l10n de 0003', u'l10n de 0002'])
def confirm_ship_mstone(request): """Intermediate page when shipping a milestone. Gathers all data to verify when shipping. Ends up in ship_mstone if everything is fine. Redirects to milestones() in case of trouble. """ if not request.GET.get('ms'): raise http.Http404("ms must be supplied") mstone = get_object_or_404(Milestone, code=request.GET['ms']) if mstone.status != Milestone.OPEN: return http.HttpResponseRedirect(reverse('shipping.views.milestones')) flags4loc = (flags4appversions( appversions={'id': mstone.appver.id})[mstone.appver]) pending_locs = [] good = 0 for loc, (real_av, flags) in flags4loc.iteritems(): if real_av == mstone.appver.code and Action.PENDING in flags: # pending pending_locs.append(loc) if Action.ACCEPTED in flags: # good good += 1 pending_locs.sort() return render( request, 'shipping/confirm-ship.html', { 'mstone': mstone, 'pending_locs': pending_locs, 'good': good, 'login_form_needs_reload': True, 'request': request, })
def testOneOld(self): """One locale signed off and accepted on old appversion, nothing new on new, thus falling back to the old one. """ locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.ACCEPTED, None) self.assertEqual(repo.changesets.count(), 2) flaglocs4av, not_found = _actions4appversion(self.old_av, {locale.id}, None, 100) self.assertEqual(not_found, set()) self.assertListEqual(list(flaglocs4av.keys()), [locale.id]) flag, action_id = list(flaglocs4av[locale.id].items())[0] self.assertEqual(flag, Action.ACCEPTED) self.assertEqual( Signoff.objects.get(action=action_id).locale_id, locale.id) self.assertTupleEqual( _actions4appversion(self.new_av, {locale.id}, None, 100), ({}, {locale.id})) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual( flagdata[self.new_av], {'da': ['fx1.0', {Action.ACCEPTED: self.actions[1].id}] }) self.assertDictEqual(flagdata[self.old_av], flagdata[self.new_av])
def test_signoff_annotated_pushes(self): view = SignoffView() locale = Locale.objects.get(code='de') real_av, flags = (api.flags4appversions( [self.av], locales=[locale.id]).get(self.av, {}).get(locale.code, [None, {}])) actions = list( Action.objects.filter(id__in=flags.values()).select_related( 'signoff__push__repository', 'author')) fallback, = actions assert fallback.flag == Action.ACCEPTED, fallback.flag pushes_data = view.annotated_pushes( locale, self.av, actions=actions, flags=flags, fallback=fallback, ) suggested_signoff = pushes_data['suggested_signoff'] self.assertIsNone(suggested_signoff) pushes = pushes_data['pushes'] changesets = [c for p in pushes for c in p['changes']] revisions = [x.revision for x in changesets] # only `de` changes in the right order self.assertListEqual(revisions, ['l10n de 0003', 'l10n de 0002'])
def testOneOld(self): """One locale signed off and accepted on old appversion, nothing new on new, thus falling back to the old one. """ locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.ACCEPTED, None) eq_(repo.changesets.count(), 2) flaglocs4av, not_found = _actions4appversion(self.old_av, set([locale.id]), 100) eq_(not_found, set()) eq_(flaglocs4av.keys(), [locale.id]) flag, action_id = flaglocs4av[locale.id].items()[0] eq_(flag, Action.ACCEPTED) eq_(Signoff.objects.get(action=action_id).locale_id, locale.id) eq_(_actions4appversion(self.new_av, set([locale.id]), 100), ({}, set([locale.id]))) flagdata = flags4appversions() ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], {'da': ['fx1.0', {Action.ACCEPTED: self.actions[1].id}] }) eq_(flagdata[self.old_av], flagdata[self.new_av])
def test_signoff_annotated_pushes(self): view = SignoffView() locale = Locale.objects.get(code='de') real_av, flags = ( api.flags4appversions([self.av], locales=[locale.id]) .get(self.av, {}) .get(locale.code, [None, {}])) actions = list(Action.objects.filter(id__in=flags.values()) .select_related('signoff__push__repository', 'author')) fallback, = actions assert fallback.flag == Action.ACCEPTED, fallback.flag pushes_data = view.annotated_pushes( locale, self.av, actions=actions, flags=flags, fallback=fallback, ) suggested_signoff = pushes_data['suggested_signoff'] self.assertIsNone(suggested_signoff) pushes = pushes_data['pushes'] changesets = [c for p in pushes for c in p['changes']] revisions = [x.revision for x in changesets] # only `de` changes in the right order self.assertListEqual(revisions, ['l10n de 0003', 'l10n de 0002'])
def _get_flags_and_actions(self): __, flags = (api.flags4appversions( locales=self._locale_search, appversions=self._appver_search) .get(self.av, {}) .get(self.locale.code, [None, {}])) actions = Action.objects.filter(id__in=flags.values()) return flags, actions
def testOneOldObsoleted(self): locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.OBSOLETED, None) eq_(repo.changesets.count(), 2) flagdata = flags4appversions() ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], {}) eq_(flagdata[self.old_av], {})
def testOneOldObsoleted(self): locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.OBSOLETED, None) self.assertEqual(repo.changesets.count(), 2) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual(flagdata[self.new_av], {}) self.assertDictEqual(flagdata[self.old_av], {})
def test_getflags(self): """Test that the list returns the right flags.""" flags = flags4appversions(appversions={"code": "fx1.0"}) av = AppVersion.objects.get(code="fx1.0") eq_(flags, {av: { "pl": ["fx1.0", {Action.PENDING: 2}], "de": ["fx1.0", {Action.ACCEPTED: 3}], "fr": ["fx1.0", {Action.REJECTED: 5}], "da": ["fx1.0", {Action.ACCEPTED: 8, Action.PENDING: 7}] }})
def test_getflags(self): """Test that the list returns the right flags.""" av = AppVersion.objects.get(code="fx1.0") flags = flags4appversions([av], locales=list(range(1, 5))) self.assertDictEqual(flags, {av: { "pl": ["fx1.0", {Action.PENDING: 2}], "de": ["fx1.0", {Action.ACCEPTED: 3}], "fr": ["fx1.0", {Action.REJECTED: 5}], "da": ["fx1.0", {Action.ACCEPTED: 8, Action.PENDING: 7}] }})
def test_getflags(self): """Test that the list returns the right flags.""" av = AppVersion.objects.get(code="fx1.0") flags = flags4appversions([av], locales=range(1, 5)) eq_(flags, {av: { "pl": ["fx1.0", {Action.PENDING: 2}], "de": ["fx1.0", {Action.ACCEPTED: 3}], "fr": ["fx1.0", {Action.REJECTED: 5}], "da": ["fx1.0", {Action.ACCEPTED: 8, Action.PENDING: 7}] }})
def testOneOldAndOtherNew(self): da = Locale.objects.get(code='da') de = Locale.objects.get(code='de') repo = self._setup(da, Action.ACCEPTED, None) self.assertEqual(repo.changesets.count(), 2) repo = self._setup(de, None, Action.ACCEPTED) self.assertEqual(repo.changesets.count(), 2) a4av, not_found = _actions4appversion(self.old_av, {da.id, de.id}, None, 100) self.assertSetEqual(not_found, {de.id}) self.assertListEqual(a4av.keys(), [da.id]) flag, action_id = a4av[da.id].items()[0] self.assertEqual(flag, Action.ACCEPTED) a4av, not_found = _actions4appversion(self.new_av, {da.id, de.id}, None, 100) self.assertSetEqual(not_found, {da.id}) self.assertListEqual(a4av.keys(), [de.id]) flag, action_id = a4av[de.id].items()[0] self.assertEqual(flag, Action.ACCEPTED) a4av, not_found = _actions4appversion(self.old_av, {da.id, de.id}, None, 100) self.assertSetEqual(not_found, {de.id}) self.assertListEqual(a4av.keys(), [da.id]) flag, action_id = a4av[da.id].items()[0] self.assertEqual(flag, Action.ACCEPTED) a4avs = actions4appversions(appversions=[self.new_av], locales=[da.id, de.id]) self.assertEqual(len(a4avs), 2) self.assertIn(self.old_av, a4avs) self.assertIn(self.new_av, a4avs) a4av = a4avs[self.new_av] self.assertEqual(len(a4av), 1) a4av = a4avs[self.old_av] self.assertEqual(len(a4av), 1) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual( flagdata[self.new_av], { 'da': ['fx1.0', { Action.ACCEPTED: self.actions[1].id }], 'de': ['fx1.1', { Action.ACCEPTED: self.actions[3].id }] }) self.assertDictEqual( flagdata[self.old_av], {'da': ['fx1.0', { Action.ACCEPTED: self.actions[1].id }]})
def testOneOldAndNewObsoleted(self): locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.ACCEPTED, Action.OBSOLETED) eq_(repo.changesets.count(), 3) flagdata = flags4appversions() ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], {}) eq_(flagdata[self.old_av], {'da': ['fx1.0', {Action.ACCEPTED: self.actions[1].id}] })
def testOneOldAndNewObsoleted(self): locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.ACCEPTED, Action.OBSOLETED) eq_(repo.changesets.count(), 3) flagdata = flags4appversions() ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], {}) eq_(flagdata[self.old_av], {'da': ['fx1.0', { Action.ACCEPTED: self.actions[1].id }]})
def testOneOldAndOtherNew(self): da = Locale.objects.get(code='da') de = Locale.objects.get(code='de') repo = self._setup(da, Action.ACCEPTED, None) eq_(repo.changesets.count(), 2) repo = self._setup(de, None, Action.ACCEPTED) eq_(repo.changesets.count(), 2) a4av, not_found = _actions4appversion(self.old_av, set([da.id, de.id]), 100) eq_(not_found, set([de.id])) eq_(a4av.keys(), [da.id]) flag, action_id = a4av[da.id].items()[0] eq_(flag, Action.ACCEPTED) a4av, not_found = _actions4appversion(self.new_av, set([da.id, de.id]), 100) eq_(not_found, set([da.id])) eq_(a4av.keys(), [de.id]) flag, action_id = a4av[de.id].items()[0] eq_(flag, Action.ACCEPTED) a4av, not_found = _actions4appversion(self.old_av, set([da.id, de.id]), 100) eq_(not_found, set([de.id])) eq_(a4av.keys(), [da.id]) flag, action_id = a4av[da.id].items()[0] eq_(flag, Action.ACCEPTED) a4avs = actions4appversions(appversions=[self.new_av], locales=[da.id, de.id]) eq_(len(a4avs), 2) ok_(self.old_av in a4avs) ok_(self.new_av in a4avs) a4av = a4avs[self.new_av] eq_(len(a4av), 1) a4av = a4avs[self.old_av] eq_(len(a4av), 1) flagdata = flags4appversions() ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_( flagdata[self.new_av], { 'da': ['fx1.0', { Action.ACCEPTED: self.actions[1].id }], 'de': ['fx1.1', { Action.ACCEPTED: self.actions[3].id }] }) eq_(flagdata[self.old_av], {'da': ['fx1.0', { Action.ACCEPTED: self.actions[1].id }]})
def testEmpty(self): locale = Locale.objects.get(code='da') repo = self._setup(locale, None, None) eq_(repo.changesets.count(), 1) eq_(_actions4appversion(self.old_av, set([locale.id]), 100), ({}, set([locale.id]))) eq_(_actions4appversion(self.new_av, set([locale.id]), 100), ({}, set([locale.id]))) flagdata = flags4appversions() ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], {}) eq_(flagdata[self.old_av], flagdata[self.new_av])
def testOneOldAndNew(self): locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.ACCEPTED, Action.ACCEPTED) eq_(repo.changesets.count(), 3) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], {'da': ['fx1.1', {Action.ACCEPTED: self.actions[3].id}] }) eq_(flagdata[self.old_av], {'da': ['fx1.0', {Action.ACCEPTED: self.actions[1].id}] })
def testOneOldAndNewObsoleted(self): locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.ACCEPTED, Action.OBSOLETED) self.assertEqual(repo.changesets.count(), 3) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual(flagdata[self.new_av], {}) self.assertDictEqual( flagdata[self.old_av], {'da': ['fx1.0', {Action.ACCEPTED: self.actions[1].id}] })
def testOneOldAndNewObsoleted(self): locale = Locale.objects.get(code='da') repo = self._setup(locale, Action.ACCEPTED, Action.OBSOLETED) self.assertEqual(repo.changesets.count(), 3) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual(flagdata[self.new_av], {}) self.assertDictEqual( flagdata[self.old_av], {'da': ['fx1.0', { Action.ACCEPTED: self.actions[1].id }]})
def testOneOldAndOtherNew(self): da = Locale.objects.get(code='da') de = Locale.objects.get(code='de') repo = self._setup(da, Action.ACCEPTED, None) eq_(repo.changesets.count(), 2) repo = self._setup(de, None, Action.ACCEPTED) eq_(repo.changesets.count(), 2) a4av, not_found = _actions4appversion(self.old_av, {da.id, de.id}, None, 100) eq_(not_found, {de.id}) eq_(a4av.keys(), [da.id]) flag, action_id = a4av[da.id].items()[0] eq_(flag, Action.ACCEPTED) a4av, not_found = _actions4appversion(self.new_av, {da.id, de.id}, None, 100) eq_(not_found, {da.id}) eq_(a4av.keys(), [de.id]) flag, action_id = a4av[de.id].items()[0] eq_(flag, Action.ACCEPTED) a4av, not_found = _actions4appversion(self.old_av, {da.id, de.id}, None, 100) eq_(not_found, {de.id}) eq_(a4av.keys(), [da.id]) flag, action_id = a4av[da.id].items()[0] eq_(flag, Action.ACCEPTED) a4avs = actions4appversions(appversions=[self.new_av], locales=[da.id, de.id]) eq_(len(a4avs), 2) ok_(self.old_av in a4avs) ok_(self.new_av in a4avs) a4av = a4avs[self.new_av] eq_(len(a4av), 1) a4av = a4avs[self.old_av] eq_(len(a4av), 1) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], { 'da': ['fx1.0', {Action.ACCEPTED: self.actions[1].id}], 'de': ['fx1.1', {Action.ACCEPTED: self.actions[3].id}] }) eq_(flagdata[self.old_av], { 'da': ['fx1.0', {Action.ACCEPTED: self.actions[1].id}] })
def testOneOldAndOtherNew(self): da = Locale.objects.get(code='da') de = Locale.objects.get(code='de') repo = self._setup(da, Action.ACCEPTED, None) self.assertEqual(repo.changesets.count(), 2) repo = self._setup(de, None, Action.ACCEPTED) self.assertEqual(repo.changesets.count(), 2) a4av, not_found = _actions4appversion(self.old_av, {da.id, de.id}, None, 100) self.assertSetEqual(not_found, {de.id}) self.assertListEqual(list(a4av.keys()), [da.id]) flag, action_id = list(a4av[da.id].items())[0] self.assertEqual(flag, Action.ACCEPTED) a4av, not_found = _actions4appversion(self.new_av, {da.id, de.id}, None, 100) self.assertSetEqual(not_found, {da.id}) self.assertListEqual(list(a4av.keys()), [de.id]) flag, action_id = list(a4av[de.id].items())[0] self.assertEqual(flag, Action.ACCEPTED) a4av, not_found = _actions4appversion(self.old_av, {da.id, de.id}, None, 100) self.assertSetEqual(not_found, {de.id}) self.assertListEqual(list(a4av.keys()), [da.id]) flag, action_id = list(a4av[da.id].items())[0] self.assertEqual(flag, Action.ACCEPTED) a4avs = actions4appversions(appversions=[self.new_av], locales=[da.id, de.id]) self.assertEqual(len(a4avs), 2) self.assertIn(self.old_av, a4avs) self.assertIn(self.new_av, a4avs) a4av = a4avs[self.new_av] self.assertEqual(len(a4av), 1) a4av = a4avs[self.old_av] self.assertEqual(len(a4av), 1) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual(flagdata[self.new_av], { 'da': ['fx1.0', {Action.ACCEPTED: self.actions[1].id}], 'de': ['fx1.1', {Action.ACCEPTED: self.actions[3].id}] }) self.assertDictEqual(flagdata[self.old_av], { 'da': ['fx1.0', {Action.ACCEPTED: self.actions[1].id}] })
def testEmpty(self): locale = Locale.objects.get(code='da') repo = self._setup(locale, None, None) self.assertEqual(repo.changesets.count(), 1) self.assertTupleEqual( _actions4appversion(self.old_av, {locale.id}, None, 100), ({}, {locale.id})) self.assertTupleEqual( _actions4appversion(self.new_av, {locale.id}, None, 100), ({}, {locale.id})) avs = AppVersion.objects.all() flagdata = flags4appversions(avs) self.assertIn(self.old_av, flagdata) self.assertIn(self.new_av, flagdata) self.assertEqual(len(flagdata), 2) self.assertDictEqual(flagdata[self.new_av], {}) self.assertDictEqual(flagdata[self.old_av], flagdata[self.new_av])
def get_context_data(self, lang, appver): # which pushes to show real_av, flags = (flags4appversions(locales={ 'id': lang.id }, appversions={ 'id': appver.id }).get(appver, {}).get( lang.code, [None, {}])) actions = list( Action.objects.filter(id__in=flags.values()).select_related( 'signoff__push__repository', 'author')) # get current status of signoffs push4action = dict((a.id, a.signoff.push) for a in actions) pending = push4action.get(flags.get(Action.PENDING)) rejected = push4action.get(flags.get(Action.REJECTED)) accepted = push4action.get(flags.get(Action.ACCEPTED)) if real_av != appver.code and accepted is not None: # we're falling back, add the accepted push to the table fallback = accepted else: fallback = None pushes, suggested_signoff = self.annotated_pushes( actions, flags, fallback, lang, appver, ) return { 'appver': appver, 'language': lang, 'pushes': pushes, 'pending': pending, 'rejected': rejected, 'accepted': accepted, 'suggested_signoff': suggested_signoff, 'login_form_needs_reload': True, 'fallback': fallback, 'real_av': real_av, }
def signoff(request, locale_code, app_code): """View to show recent sign-offs and opportunities to sign off. This view is the main entry point to localizers to add sign-offs and to review what they're shipping. It's also the entry point for drivers to review existing sign-offs. """ appver = get_object_or_404(AppVersion, code=app_code) lang = get_object_or_404(Locale, code=locale_code) # which pushes to show real_av, flags = (flags4appversions( locales={'id': lang.id}, appversions={'id': appver.id}) .get(appver, {}) .get(lang.code, [None, {}])) actions = list(Action.objects.filter(id__in=flags.values()) .select_related('signoff__push__repository', 'author')) # get current status of signoffs push4action = dict((a.id, a.signoff.push) for a in actions) pending = push4action.get(flags.get(Action.PENDING)) rejected = push4action.get(flags.get(Action.REJECTED)) accepted = push4action.get(flags.get(Action.ACCEPTED)) if real_av != appver.code and accepted is not None: # we're falling back, add the accepted push to the table fallback = accepted else: fallback = None pushes, suggested_signoff = annotated_pushes(appver, lang, actions, flags, fallback) return render(request, 'shipping/signoffs.html', { 'appver': appver, 'language': lang, 'pushes': pushes, 'pending': pending, 'rejected': rejected, 'accepted': accepted, 'suggested_signoff': suggested_signoff, 'login_form_needs_reload': True, 'fallback': fallback, 'real_av': real_av, })
def get_context_data(self, lang, appver): # which pushes to show real_av, flags = (flags4appversions( locales={'id': lang.id}, appversions={'id': appver.id}) .get(appver, {}) .get(lang.code, [None, {}])) actions = list(Action.objects.filter(id__in=flags.values()) .select_related('signoff__push__repository', 'author')) # get current status of signoffs push4action = dict((a.id, a.signoff.push) for a in actions) pending = push4action.get(flags.get(Action.PENDING)) rejected = push4action.get(flags.get(Action.REJECTED)) accepted = push4action.get(flags.get(Action.ACCEPTED)) if real_av != appver.code and accepted is not None: # we're falling back, add the accepted push to the table fallback = accepted else: fallback = None pushes, suggested_signoff = self.annotated_pushes( actions, flags, fallback, lang, appver, ) return { 'appver': appver, 'language': lang, 'pushes': pushes, 'pending': pending, 'rejected': rejected, 'accepted': accepted, 'suggested_signoff': suggested_signoff, 'login_form_needs_reload': True, 'fallback': fallback, 'real_av': real_av, }
def testOneNew(self): """One accepted signoff on the new appversion, none on the old. Old appversion comes back empty. """ locale = Locale.objects.get(code='da') repo = self._setup(locale, None, Action.ACCEPTED) eq_(repo.changesets.count(), 2) eq_(_actions4appversion(self.old_av, set([locale.id]), 100), ({}, set([locale.id]))) a4av, not_found = _actions4appversion(self.new_av, set([locale.id]), 100) eq_(not_found, set()) eq_(a4av.keys(), [locale.id]) flag, action_id = a4av[locale.id].items()[0] eq_(flag, Action.ACCEPTED) eq_(Signoff.objects.get(action=action_id).locale_id, locale.id) flagdata = flags4appversions() ok_(self.old_av in flagdata) ok_(self.new_av in flagdata) eq_(len(flagdata), 2) eq_(flagdata[self.new_av], {'da': ['fx1.1', {Action.ACCEPTED: self.actions[1].id}]}) eq_(flagdata[self.old_av], {})
def test_signoff_annotated_pushes(self): view = SignoffView() locale = Locale.objects.get(code='de') real_av, flags = (api.flags4appversions(locales={ 'id': locale.id }, appversions={ 'id': self.av.id }).get(self.av, {}).get( locale.code, [None, {}])) actions = list( Action.objects.filter(id__in=flags.values()).select_related( 'signoff__push__repository', 'author')) fallback, = actions assert fallback.flag == Action.ACCEPTED, fallback.flag pushes, suggested_signoff = view.annotated_pushes( actions, flags, fallback, locale, self.av) eq_(suggested_signoff, None) changesets = [c for p in pushes for c in p['changes']] revisions = [x.revision for x in changesets] # only `de` changes in the right order eq_(revisions, [u'l10n de 0003', u'l10n de 0002'])
def get_signoffs(self): avq = defaultdict(set) if self.avs: for appver in AppVersion.objects.filter(code__in=self.avs).values("id"): avq["id__in"].add(appver["id"]) if self.trees: av_ids = ( AppVersion.trees.through.objects.current() .filter(tree__code__in=self.trees) .values_list("appversion_id", flat=True) ) avq["id__in"].update(av_ids) # restrict avq to building appversions open to signoffs currently_building = set( AppVersion.trees.through.objects.current() .filter(appversion__accepts_signoffs=True) .values_list("appversion_id", flat=True) ) if avq: avq["id__in"] &= currently_building else: avq["id__in"] = currently_building apps = list(AppVersion.objects.filter(**avq).values_list("app", flat=True).distinct()) if len(apps) == 1: given_app = Application.objects.get(id=apps[0]).code else: given_app = None if apps: appvers = AppVersion.objects.filter(app__in=apps) else: appvers = AppVersion.objects.all() lq = {} if self.locales: lq["code__in"] = self.locales locflags4av = flags4appversions(locales=lq, appversions=avq) tree_avs = AppVersion.trees.through.objects.current().filter(appversion__in=appvers) tree2av = dict(tree_avs.values_list("tree__code", "appversion__code")) av2tree = dict((av, tree) for tree, av in tree2av.iteritems()) tree2app = dict(tree_avs.values_list("tree__code", "appversion__app__code")) values = dict(Action._meta.get_field("flag").flatchoices) so_items = {} for av in AppVersion.objects.filter(**avq): for loc, (real_av, flags) in locflags4av[av].iteritems(): flag_values = [(real_av == av.code or f != Action.ACCEPTED) and values[f] or real_av for f in flags] so_items[(av2tree[av.code], loc)] = flag_values # get shipped-in data, latest milestone of all appversions for now shipped_in = defaultdict(list) for _av in appvers.select_related("app"): for _ms in _av.milestone_set.filter(status=2).order_by("-pk")[:1]: break else: continue app = _av.app.code _sos = _ms.signoffs if self.locales: _sos = _sos.filter(locale__code__in=self.locales) for loc in _sos.values_list("locale__code", flat=True): shipped_in[(app, loc)].append(_av.code) # make a list now items = [ { "type": "SignOff", "label": "%s/%s" % (tree, locale), "tree": tree, "apploc": ("%s::%s" % (given_app or tree2app[tree], locale)), "signoff": sorted(values), } for (tree, locale), values in sorted(so_items.iteritems(), key=lambda t: t[0]) ] items += [ {"type": "Shippings", "label": "%s::%s" % (av, locale), "shipped": stones} for (av, locale), stones in shipped_in.iteritems() ] items += [{"type": "AppVer4Tree", "label": tree, "appversion": av} for tree, av in tree2av.iteritems()] return items
def get_signoffs(self): avq = defaultdict(set) if self.avs: for appver in (AppVersion.objects.filter( code__in=self.avs).values('id')): avq['id__in'].add(appver['id']) if self.trees: av_ids = (AppVersion.trees.through.objects.current().filter( tree__code__in=self.trees).values_list('appversion_id', flat=True)) avq['id__in'].update(av_ids) # restrict avq to building appversions open to signoffs currently_building = set( AppVersion.trees.through.objects.current().filter( appversion__accepts_signoffs=True).values_list('appversion_id', flat=True)) if avq: avq['id__in'] &= currently_building else: avq['id__in'] = currently_building apps = list( AppVersion.objects.filter(**avq).values_list('app', flat=True).distinct()) if len(apps) == 1: given_app = Application.objects.get(id=apps[0]).code else: given_app = None if apps: appvers = AppVersion.objects.filter(app__in=apps) else: appvers = AppVersion.objects.all() lq = {} if self.locales: lq['code__in'] = self.locales locflags4av = flags4appversions(locales=lq, appversions=avq) tree_avs = (AppVersion.trees.through.objects.current().filter( appversion__in=appvers)) tree2av = dict(tree_avs.values_list("tree__code", "appversion__code")) av2tree = dict((av, tree) for tree, av in tree2av.iteritems()) tree2app = dict( tree_avs.values_list("tree__code", "appversion__app__code")) values = dict(Action._meta.get_field('flag').flatchoices) so_items = {} for av in AppVersion.objects.filter(**avq): for loc, (real_av, flags) in locflags4av[av].iteritems(): flag_values = [ (real_av == av.code or f != Action.ACCEPTED) and values[f] or real_av for f in flags ] so_items[(av2tree[av.code], loc)] = flag_values # get shipped-in data, latest milestone of all appversions for now shipped_in = defaultdict(list) for _av in appvers.select_related('app'): for _ms in _av.milestone_set.filter(status=2).order_by('-pk')[:1]: break else: continue app = _av.app.code _sos = _ms.signoffs if self.locales: _sos = _sos.filter(locale__code__in=self.locales) for loc in _sos.values_list('locale__code', flat=True): shipped_in[(app, loc)].append(_av.code) # make a list now items = [{ "type": "SignOff", "label": "%s/%s" % (tree, locale), "tree": tree, "apploc": ("%s::%s" % (given_app or tree2app[tree], locale)), "signoff": sorted(values) } for ( tree, locale), values in sorted(so_items.iteritems(), key=lambda t: t[0]) ] items += [{ "type": "Shippings", "label": "%s::%s" % (av, locale), "shipped": stones } for (av, locale), stones in shipped_in.iteritems()] items += [{ "type": "AppVer4Tree", "label": tree, "appversion": av } for tree, av in tree2av.iteritems()] return items
def get_context_data(self, lang, appver): # which pushes to show real_av, flags = (flags4appversions([appver], locales=[lang.id]) .get(appver, {}) .get(lang.code, [None, {}])) actions = list(Action.objects.filter(id__in=flags.values()) .select_related('signoff__push__repository', 'author')) # get current status of signoffs push4action = dict((a.id, a.signoff.push) for a in actions) pending = push4action.get(flags.get(Action.PENDING)) rejected = push4action.get(flags.get(Action.REJECTED)) accepted = push4action.get(flags.get(Action.ACCEPTED)) if real_av != appver.code and accepted is not None: # we're falling back, add the accepted push to the table fallback = accepted else: fallback = None pushes_data = self.annotated_pushes( lang, appver, actions=actions, flags=flags, fallback=fallback, count=self.count, ) # Check if this is the very first release. # Only applies to products that have a fallback (Firefox for example). first = appver.fallback is not None and accepted is None try: team_locale = ( TeamLocaleThrough.objects.current().get(locale=lang).team ) except TeamLocaleThrough.DoesNotExist: team_locale = lang if pushes_data['next_push_date']: next_push_date = pushes_data['next_push_date'].isoformat() else: next_push_date = None return { 'appver': appver, 'language': lang, 'team_locale': team_locale, 'pushes': pushes_data['pushes'], 'pushes_left': pushes_data['pushes_left'], 'next_push_date': next_push_date, 'pending': pending, 'rejected': rejected, 'accepted': accepted, 'first': first, 'suggested_signoff': pushes_data['suggested_signoff'], 'login_form_needs_reload': True, 'fallback': fallback, 'real_av': real_av, }
def get_signoffs(self): avq = defaultdict(set) if self.avs: for appver in (AppVersion.objects .filter(code__in=self.avs).values('id')): avq['id__in'].add(appver['id']) if self.trees: av_ids = (AppVersion.trees.through.objects .current() .filter(tree__code__in=self.trees) .values_list('appversion_id', flat=True)) avq['id__in'].update(av_ids) # restrict avq to building appversions open to signoffs currently_building = set(AppVersion.trees.through.objects .current() .filter(appversion__accepts_signoffs=True) .values_list('appversion_id', flat=True)) if avq: avq['id__in'] &= currently_building else: avq['id__in'] = currently_building apps = list(AppVersion.objects .filter(**avq) .values_list('app', flat=True) .distinct()) if len(apps) == 1: given_app = Application.objects.get(id=apps[0]).code else: given_app = None if apps: appvers = AppVersion.objects.filter(app__in=apps) else: appvers = AppVersion.objects.all() lq = {} if self.locales: lq['code__in'] = self.locales locflags4av = flags4appversions(locales=lq, appversions=avq) tree_avs = (AppVersion.trees.through.objects .current() .filter(appversion__in=appvers)) tree2av = dict(tree_avs.values_list("tree__code", "appversion__code")) av2tree = dict((av, tree) for tree, av in tree2av.iteritems()) tree2app = dict(tree_avs.values_list("tree__code", "appversion__app__code")) values = dict(Action._meta.get_field('flag').flatchoices) so_items = {} for av in AppVersion.objects.filter(**avq): for loc, (real_av, flags) in locflags4av[av].iteritems(): flag_values = [ (real_av == av.code or f != Action.ACCEPTED) and values[f] or real_av for f in flags] so_items[(av2tree[av.code], loc)] = flag_values # make a list now items = [{"type": "SignOff", "label": "%s/%s" % (tree, locale), "tree": tree, "apploc": ("%s::%s" % (given_app or tree2app[tree], locale)), "signoff": sorted(values)} for (tree, locale), values in sorted(so_items.iteritems(), key=lambda t:t[0])] items += [{"type": "AppVer4Tree", "label": tree, "appversion": av} for tree, av in tree2av.iteritems()] return items
def get_signoffs(self): appversions_with_pushes = [] _treeid_to_avt = {} tree_ids = {} avts = (AppVersion.trees.through.objects.current().filter( appversion__accepts_signoffs=True)) if self.avs: avts = avts.filter(appversion__code__in=self.avs) for avt in avts.select_related('appversion__app', 'tree__l10n'): _treeid_to_avt[avt.tree.id] = avt tree_ids[avt.tree.code] = avt.tree.id appvers = [avt.appversion for avt in _treeid_to_avt.values()] locales = None if self.locales: locales = list( Locale.objects.filter(code__in=self.locales).values_list( 'id', flat=True)) locflags4av = flags4appversions(appvers, locales=locales) av2tree = { avt.appversion.code: avt.tree.code for avt in six.itervalues(_treeid_to_avt) } values = dict(Action._meta.get_field('flag').flatchoices) so_items = {} actions = {} for av in appvers: for loc, (real_av, flags) in six.iteritems(locflags4av[av]): flag_values = [ (real_av == av.code or f != Action.ACCEPTED) and values[f] or real_av for f in flags ] item = {'flags': flag_values, 'state': None, 'action': []} if Action.ACCEPTED in flags: item['state'] = (real_av == av.code) and 'OK' or real_av if Action.REJECTED in flags: item['action'].append('rejected') if Action.PENDING in flags: item['action'].append('review') so_items[(av2tree[av.code], loc)] = item for flag, action in flags.items(): # don't keep track of fallback actions if not (real_av != av.code and flag == Action.ACCEPTED): actions[action] = [av, loc] for action, signoff, push_date in (Action.objects.filter( id__in=actions.keys()).values_list( 'id', 'signoff', 'signoff__push__push_date')): actions[action] += [signoff, push_date] last_action = {} last_signoff = {} for action, (av, loc, signoff, push_date) in sorted(six.iteritems(actions), key=lambda t: t[1][3], reverse=True): if (av, loc) in last_action: continue last_action[(av, loc)] = push_date last_signoff[(av, loc)] = signoff runs_with_open_av = [ run for run, tree in self.runids2tree.items() if tree in tree_ids and _treeid_to_avt[tree_ids[tree]].appversion.accepts_signoffs ] changesets = { tuple(t[:2]): t[2] for t in Run.revisions.through.objects.filter( run__in=runs_with_open_av, changeset__repositories__locale__isnull=False).values_list( 'run__tree', 'run__locale__code', 'changeset') } pushdates = { tuple(t[:2]): t[2] for t in Push.changesets.through.objects.filter( changeset__in=set(changesets.values())).values_list( 'changeset', 'push__repository__forest', 'push__push_date') } avl_has_new_run = set() maybe_new_run = [] for (treeid, locale_code), changeset in six.iteritems(changesets): avt = _treeid_to_avt[treeid] push_date = pushdates[(changeset, avt.tree.l10n.id)] if avt.start and push_date < avt.start: # need update appversions_with_pushes.append({ "needs_update": True, "type": "NewPush", "label": avt.tree.code + '/' + locale_code }) continue if avt.end and push_date > avt.end: continue if ((avt.appversion, locale_code) in last_action and last_action[(avt.appversion, locale_code)] >= push_date): continue maybe_new_run.append( (avt.appversion, changeset, avt.tree.code, locale_code, last_signoff.get((avt.appversion, locale_code)))) # find out if the sign-off is on the same revision as the last push old_signoffs = [t[4] for t in maybe_new_run if t[4] is not None] signoffs_to_skip = set() if old_signoffs: so_tips = dict( Signoff.objects.filter(id__in=old_signoffs).annotate( cs=Max("push__changesets")).values_list('id', 'cs')) latest_changeset = dict((t[4], t[1]) for t in maybe_new_run) for so_id, tip in six.iteritems(so_tips): if latest_changeset[so_id] == tip: signoffs_to_skip.add(so_id) for av, changeset, tree_code, locale_code, last_so in maybe_new_run: if last_so in signoffs_to_skip: continue avl_has_new_run.add((tree_code, locale_code)) appversions_with_pushes.append({ "new_run": "sign off", "type": "NewPush", "label": tree_code + '/' + locale_code }) # make a list now items = [] for (tree, locale), values in sorted(six.iteritems(so_items), key=lambda t: t[0]): glyph = '' if values['state'] == 'OK': glyph = 'check' if (tree, locale) in avl_has_new_run: glyph = 'graph' elif values['state']: glyph = 'warning' item = { "type": "SignOff", "label": "%s/%s" % (tree, locale), "tree": tree, "state": values['state'], "state_glyph": glyph, "signoff": sorted(values['flags']) } if values['action']: item['action'] = values['action'] items.append(item) items += [{ "type": "AppVer4Tree", "label": tree, "appversion": av } for av, tree in six.iteritems(av2tree)] items += appversions_with_pushes return items
def teamsnippet(loc, team_locales): locs = Locale.objects.filter(pk__in=[loc.pk] + list(team_locales)) runs = sorted( (Run.objects .filter(locale__in=locs, active__isnull=False) .select_related('tree', 'locale')), key=lambda r: (r.tree.code, '' if r.locale.code == loc.code else r.locale.code) ) # this locale isn't active in our trees yet if not runs: return '' # Create these two based on all appversions attached to a tree so that in # the big loop on runs we don't need to make excessive queries for # appversions and applications _trees_to_appversions = {} appversion_has_pushes = {} for avt in (AppVersion.trees.through.objects .current() .select_related('appversion__app', 'tree__l10n')): _trees_to_appversions[avt.tree] = avt.appversion # find out if the appversion has activity (if open for sign-offs) if not avt.appversion.accepts_signoffs: continue pushes = Push.objects.filter(repository__forest=avt.tree.l10n) pushes = pushes.filter(repository__locale__in=locs) if avt.start: pushes = pushes.filter(push_date__gt=avt.start) if avt.end: pushes = pushes.filter(push_date__lt=avt.end) locales_with_runs = (Run.revisions.through.objects .filter(run__tree=avt.tree, changeset__pushes__in=pushes) .values_list('run__locale', flat=True)) for locale_id in locales_with_runs: appversion_has_pushes[(avt.appversion, locale_id)] = True def tree_to_appversion(tree): return _trees_to_appversions.get(tree) def tree_to_application(tree): av = tree_to_appversion(tree) return av and av.app or None # offer all revisions to sign-off. # in api.annotated_pushes, we only highlight the latest run if it's green suggested_runs = runs suggested_rev = dict(Run_Revisions.objects .filter(run__in=suggested_runs, changeset__repositories__locale__in=locs) .values_list('run_id', 'changeset__revision')) progresses = dict( ((pp.tree.code, pp.locale.code), pp) for pp in ( ProgressPosition.objects .filter(locale__in=locs) .select_related('tree', 'locale') ) ) applications = defaultdict(list) pushes = set() for run_ in runs: # copy the Run instance into a fancy dict but only copy those whose # key doesn't start with an underscore run = RunElement(dict((k, getattr(run_, k)) for k in run_.__dict__ if not k.startswith('_'))) run.locale = run_.locale run.allmissing = run_.allmissing # a @property of the Run model run.tree = run_.tree # foreign key lookup application = tree_to_application(run_.tree) run.changed_ratio = run.completion run.unchanged_ratio = 100 * run.unchanged / run.total run.missing_ratio = 100 * run.allmissing / run.total # cheat a bit and make sure that the red stripe on the chart is at # least 1px wide if run.allmissing and run.missing_ratio == 0: run.missing_ratio = 1 for ratio in (run.changed_ratio, run.unchanged_ratio): if ratio: ratio -= 1 break run.prog_pos = progresses.get((run.tree.code, run.locale.code)) appversion = tree_to_appversion(run.tree) # because Django templates (stupidly) swallows lookup errors we # have to apply the missing defaults too defaults = ( "pending", "actions", "accepted", "suggested_shortrev", "is_active", "under_review", "suggest_glyph", "suggest_class", "fallback") for attr in defaults: setattr(run, attr, None) run.appversion = appversion if appversion and appversion.accepts_signoffs: run.is_active = \ appversion_has_pushes.get((appversion, run.locale_id)) real_av, flags = ( flags4appversions( locales={'id': run_.locale.id}, appversions={'id': appversion.id} ) .get(appversion, {}) .get(run_.locale.code, [None, {}]) ) # get current status of signoffs # we really only need the shortrevs, we'll get those below if flags: interesting_flags = (Action.PENDING, Action.ACCEPTED, Action.REJECTED) actions = list(Action.objects .filter(id__in=flags.values(), flag__in=interesting_flags) .select_related('signoff__push') .order_by('when')) # only keep a rejected sign-off it's the last if (Action.REJECTED in flags and actions[-1].flag != Action.REJECTED): actions = filter(lambda a: a.flag != Action.REJECTED, actions) pushes.update(a.signoff.push for a in actions) objects = [RunElement( dict((k, getattr(a, k)) for k in a.__dict__ if not k.startswith('_'))) for a in actions] for a, obj in zip(actions, objects): obj.signoff = a.signoff obj.push = a.signoff.push obj.flag_name = a.get_flag_display() run.actions = objects if Action.ACCEPTED in flags: run.accepted = [obj for obj in objects if obj.flag == Action.ACCEPTED][0] objects.remove(run.accepted) if appversion.code != real_av: run.fallback = real_av # get the suggested signoff. If there are existing actions # we'll unset it when we get the shortrevs for those below if run_.id in suggested_rev: run.suggested_shortrev = suggested_rev[run_.id][:12] if run.errors: run.suggest_glyph = 'bolt' run.suggest_class = 'error' elif run.allmissing: run.suggest_glyph = 'graph' run.suggest_class = 'warning' else: run.suggest_glyph = 'badge' run.suggest_class = 'success' applications[application].append(run) # get the tip shortrevs for all our pushes pushes = map(lambda p: p.id, filter(None, pushes)) tip4push = dict(Push.objects .annotate(tc=Max('changesets')) .filter(id__in=pushes) .values_list('id', 'tc')) rev4id = dict(Changeset.objects .filter(id__in=tip4push.values()) .values_list('id', 'revision')) for runs in applications.itervalues(): for run in runs: actions = [run.accepted] if run.accepted else [] if run.actions: actions += run.actions for action in actions: action.rev = rev4id[tip4push[action.push.id]][:12] # unset the suggestion if there's existing signoff action if action.rev == run.suggested_shortrev: run.suggested_shortrev = None # if we have a pending sign-off as the last thing, # let's say so if action.flag == Action.PENDING: run.under_review = True applications = sorted( ((k, v) for (k, v) in applications.items()), key=lambda t: t[0] and t[0].name or None ) other_team_locales = Locale.objects.filter(id__in=team_locales) progress_start = datetime.utcnow() - timedelta(days=settings.PROGRESS_DAYS) return render_to_string('shipping/team-snippet.html', {'locale': loc, 'other_team_locales': other_team_locales, 'applications': applications, 'progress_start': progress_start, })
def teamsnippet(loc): runs = list( loc.run_set.filter(active__isnull=False).select_related( 'tree').order_by('tree__code')) # this locale isn't active in our trees yet if not runs: return '' _application_codes = [(x.code, x) for x in Application.objects.all()] # sort their keys so that the longest application codes come first # otherwise, suppose we had these keys: # 'fe', 'foo', 'fennec' then when matching against a tree code called # 'fennec_10x' then, it would "accidentally" match on 'fe' and not # 'fennec'. _application_codes.sort(lambda x, y: -cmp(len(x[0]), len(y[0]))) # Create these two based on all appversions attached to a tree so that in # the big loop on runs we don't need to make excessive queries for # appversions and applications _trees_to_appversions = {} for avt in (AppVersion.trees.through.objects.current().select_related( 'appversion', 'tree')): _trees_to_appversions[avt.tree] = avt.appversion def tree_to_application(tree): for key, app in _application_codes: if tree.code.startswith(key): return app def tree_to_appversion(tree): return _trees_to_appversions.get(tree) # find good revisions to sign-off, latest run needs to be green. # keep in sync with api.annotated_pushes suggested_runs = filter(lambda r: r.allmissing == 0 and r.errors == 0, runs) suggested_rev = dict( Run_Revisions.objects.filter( run__in=suggested_runs, changeset__repositories__locale=loc).values_list( 'run_id', 'changeset__revision')) applications = defaultdict(list) pushes = set() for run_ in runs: # copy the Run instance into a fancy dict but only copy those whose # key doesn't start with an underscore run = Run( dict((k, getattr(run_, k)) for k in run_.__dict__ if not k.startswith('_'))) run.allmissing = run_.allmissing # a @property of the Run model run.tree = run_.tree # foreign key lookup application = tree_to_application(run_.tree) run.changed_ratio = run.completion run.unchanged_ratio = 100 * run.unchanged / run.total run.missing_ratio = 100 * run.allmissing / run.total # cheat a bit and make sure that the red stripe on the chart is at # least 1px wide if run.allmissing and run.missing_ratio == 0: run.missing_ratio = 1 for ratio in (run.changed_ratio, run.unchanged_ratio): if ratio: ratio -= 1 break appversion = tree_to_appversion(run.tree) # because Django templates (stupidly) swallows lookup errors we # have to apply the missing defaults too run.pending = run.rejected = run.accepted = \ run.suggested_shortrev = \ run.fallback = run.appversion = None if appversion and appversion.accepts_signoffs: run.appversion = appversion real_av, flags = (flags4appversions(locales={ 'id': loc.id }, appversions={ 'id': appversion.id }).get(appversion, {}).get( loc.code, [None, {}])) # get current status of signoffs # we really only need the shortrevs, we'll get those below if flags: actions = Action.objects.filter(id__in=flags.values()) \ .select_related('signoff__push') action4id = dict((a.id, a) for a in actions) for flag in (Action.PENDING, Action.ACCEPTED, Action.REJECTED): if flag in flags: a = action4id[flags[flag]] setattr(run, a.get_flag_display(), a.signoff.push) if appversion.code != real_av: run.fallback = real_av pushes.update((run.pending, run.rejected, run.accepted)) # get the suggested signoff. If there are existing actions # we'll unset it when we get the shortrevs for those below if run_.id in suggested_rev: run.suggested_shortrev = suggested_rev[run_.id][:12] applications[application].append(run) # get the tip shortrevs for all our pushes pushes = map(lambda p: p.id, filter(None, pushes)) tip4push = dict( Push.objects.annotate(tc=Max('changesets')).filter( id__in=pushes).values_list('id', 'tc')) rev4id = dict( Changeset.objects.filter(id__in=tip4push.values()).values_list( 'id', 'revision')) for runs in applications.itervalues(): for run in runs: for k in ('pending', 'rejected', 'accepted'): if run[k] is not None: run[k + '_rev'] = rev4id[tip4push[run[k].id]][:12] # unset the suggestion if there's existing signoff action if run[k + '_rev'] == run.suggested_shortrev: run.suggested_shortrev = None applications = ((k, v) for (k, v) in applications.items()) return render_to_string('shipping/team-snippet.html', { 'locale': loc, 'applications': applications, })
def teamsnippet(loc, team_locales): locs = Locale.objects.filter(pk__in=[loc.pk] + list(team_locales)) runs = list((Run.objects.filter(locale__in=locs, active__isnull=False).select_related( 'tree', 'locale'))) # this locale isn't active in our trees yet if not runs: return { 'template': 'shipping/team-snippet.html', 'context': { 'locale': loc, 'applications': [], } } # Create these two based on all appversions attached to a tree so that in # the big loop on runs we don't need to make excessive queries for # appversions and applications appversion_has_pushes = {} _treeid_to_avt = {} for avt in (AppVersion.trees.through.objects.current().filter(tree__in=set( run.tree.id for run in runs)).select_related('appversion__app', 'tree__l10n')): _treeid_to_avt[avt.tree.id] = avt runs_with_open_av = [ run for run in runs if run.tree.id in _treeid_to_avt and _treeid_to_avt[run.tree.id].appversion.accepts_signoffs ] changesets = { tuple(t[:2]): t[2] for t in Run.revisions.through.objects.filter( run__in=runs_with_open_av, changeset__repositories__locale__in=locs).values_list( 'run__tree', 'run__locale', 'changeset') } pushdates = { tuple(t[:2]): t[2] for t in Push.changesets.through.objects.filter( changeset__in=set(changesets.values())).values_list( 'changeset', 'push__repository__forest', 'push__push_date') } for (treeid, locale_id), changeset in six.iteritems(changesets): avt = _treeid_to_avt[treeid] push_date = pushdates[(changeset, avt.tree.l10n.id)] if avt.start and push_date < avt.start: continue if avt.end and push_date > avt.end: continue appversion_has_pushes[(avt.appversion, locale_id)] = True def tree_to_appversion(tree): avt = tree.id in _treeid_to_avt and _treeid_to_avt[tree.id] return avt and avt.appversion or None def tree_to_version(tree): av = tree_to_appversion(tree) return av and av.version or None def tree_to_application(tree): av = tree_to_appversion(tree) return av and av.app or None runs.sort(key=lambda r: (tree_to_version(r.tree), r.tree.code, '' if r.locale.code == loc.code else r.locale.code)) # offer all revisions to sign-off. # in api.annotated_pushes, we only highlight the latest run if it's green suggested_runs = runs_with_open_av suggested_rev = dict( Run.revisions.through.objects.filter( run__in=suggested_runs, changeset__repositories__locale__in=locs).values_list( 'run_id', 'changeset__revision')) applications = defaultdict(list) pushes = set() flags4av = flags4appversions( [_treeid_to_avt[run.tree.id].appversion for run in runs_with_open_av], locales=list(set(run.locale.id for run in runs_with_open_av)), ) for run_ in runs: # copy the Run instance into a fancy dict but only copy those whose # key doesn't start with an underscore run = RunElement({ k: getattr(run_, k) for k in run_.__dict__ if not k.startswith('_') }) run.locale = run_.locale run.allmissing = run_.allmissing # a @property of the Run model run.tree = run_.tree # foreign key lookup application = tree_to_application(run_.tree) run.changed_ratio = run.completion run.unchanged_ratio = 100 * run.unchanged // run.total run.missing_ratio = 100 * run.allmissing // run.total # cheat a bit and make sure that the red stripe on the chart is at # least 1px wide if run.allmissing and run.missing_ratio == 0: run.missing_ratio = 1 for ratio in (run.changed_ratio, run.unchanged_ratio): if ratio: ratio -= 1 break appversion = tree_to_appversion(run.tree) # because Django templates (stupidly) swallows lookup errors we # have to apply the missing defaults too defaults = ("actions", "accepted", "suggested_shortrev", "is_active", "under_review", "suggest_glyph", "suggest_class", "fallback") for attr in defaults: setattr(run, attr, None) run.appversion = appversion run.is_active = \ appversion_has_pushes.get((appversion, run.locale_id)) applications[application].append(run) if appversion and appversion in flags4av: real_av, flags = (flags4av[appversion].get(run_.locale.code, [None, {}])) # get current status of signoffs # we really only need the shortrevs, we'll get those below if flags: interesting_flags = (Action.PENDING, Action.ACCEPTED, Action.REJECTED) actions = list( Action.objects.filter( id__in=flags.values(), flag__in=interesting_flags).select_related( 'signoff__push').order_by('when', 'pk')) # only keep a rejected sign-off it's the last if (Action.REJECTED in flags and actions[-1].flag != Action.REJECTED): actions = [a for a in actions if a.flag != Action.REJECTED] pushes.update(a.signoff.push for a in actions) objects = [ RunElement({ k: getattr(a, k) for k in a.__dict__ if not k.startswith('_') }) for a in actions ] for a, obj in zip(actions, objects): obj.signoff = a.signoff obj.push = a.signoff.push obj.flag_name = a.get_flag_display() run.actions = objects if Action.ACCEPTED in flags: run.accepted = [ obj for obj in objects if obj.flag == Action.ACCEPTED ][0] objects.remove(run.accepted) if appversion.code != real_av: run.fallback = real_av # get the suggested signoff. If there are existing actions # we'll unset it when we get the shortrevs for those below if run_.id in suggested_rev and run.is_active: run.suggested_shortrev = suggested_rev[run_.id][:12] if run.errors: run.suggest_glyph = 'bolt' run.suggest_class = 'error' elif run.allmissing: run.suggest_glyph = 'graph' run.suggest_class = 'warning' else: run.suggest_glyph = 'badge' run.suggest_class = 'success' # get the tip shortrevs for all our pushes pushes = [p.id for p in pushes if p is not None] tip4push = dict( Push.objects.annotate(tc=Max('changesets')).filter( id__in=pushes).values_list('id', 'tc')) rev4id = dict( Changeset.objects.filter(id__in=tip4push.values()).values_list( 'id', 'revision')) for runs in six.itervalues(applications): for run in runs: actions = [run.accepted] if run.accepted else [] if run.actions: actions += run.actions for action in actions: action.rev = rev4id[tip4push[action.push.id]][:12] # unset the suggestion if there's existing signoff action if action.rev == run.suggested_shortrev: run.suggested_shortrev = None run.suggest_glyph = run.suggest_class = None # if we have a pending sign-off as the last thing, # let's say so if action.flag == Action.PENDING: run.under_review = True # Sort by appname, make trees without app come last via 'ZZZZZZ' applications = sorted(((k, v) for (k, v) in applications.items()), key=lambda t: t[0] and t[0].name or 'ZZZZZZ') other_team_locales = Locale.objects.filter(id__in=team_locales) progress_start = datetime.utcnow() - timedelta(days=settings.PROGRESS_DAYS) return { 'template': 'shipping/team-snippet.html', 'context': { 'locale': loc, 'other_team_locales': other_team_locales, 'applications': applications, 'progress_start': progress_start, } }
def data(request): app_codes = request.GET.getlist('app') if not app_codes: raise Http404('List of applications required') beta_apps = Application.objects.filter(code__in=app_codes) if len(app_codes) != beta_apps.count(): raise Http404('Some of the given apps were not found') branches = request.GET.getlist('branch') f_aurora = Forest.objects.get(name='releases/l10n/mozilla-aurora') f_beta = Forest.objects.get(name='releases/l10n/mozilla-beta') forests = [] if 'aurora' in branches: forests.append(f_aurora) if 'beta' in branches: forests.append(f_beta) name4app = dict(beta_apps.values_list('id', 'name')) # get AppVersion_Trees avts = (AppVersionTreeThrough.objects.current().filter( appversion__app__in=name4app.keys(), tree__l10n__in=forests).order_by('appversion__code').select_related( 'appversion__app', 'tree')) avts = list(avts) code4av = dict((avt.appversion_id, avt.appversion.code) for avt in avts) appname4av = dict( (avt.appversion_id, name4app[avt.appversion.app_id]) for avt in avts) tree4av = dict((avt.appversion_id, avt.tree_id) for avt in avts) av4tree = dict((avt.tree_id, avt.appversion_id) for avt in avts) locs = set() loc4tree = defaultdict(list) for t, l in (Run.objects.filter(tree__in=tree4av.values(), active__isnull=False).values_list( 'tree', 'locale__code').distinct()): loc4tree[t].append(l) locs.add(l) locflags4av = flags4appversions([avt.appversion for avt in avts]) loc4tree = defaultdict(list) for av, flags4loc in locflags4av.iteritems(): if av.id not in tree4av: continue for loc, (real_av, flags) in flags4loc.iteritems(): if real_av == av.code: continue loc4tree[tree4av[av.id]].append(loc) loc4tree = dict((t, locs) for t, locs in loc4tree.iteritems() if locs) or_ = lambda l, r: l | r runqueries = reduce(or_, (reduce(or_, (Q(tree=t, locale__code=l) for l in locs)) for t, locs in loc4tree.iteritems())) actives = (Run.objects.filter(runqueries).exclude( active__isnull=True).select_related('locale')) missings = dict( ((r.tree_id, r.locale.code), r.allmissing) for r in actives) matrix = dict() columns = tuple(avt.appversion_id for avt in avts) for tree_id, locs in loc4tree.iteritems(): av_id = av4tree[tree_id] for loc in locs: if loc not in matrix: matrix[loc] = [None] * len(avts) col_index = columns.index(av_id) entry = { 'av': code4av[av_id], 'app': appname4av[av_id], 'missing': missings[(tree4av[av_id], loc)] } matrix[loc][col_index] = entry rows = [{ 'loc': loc, 'entries': matrix[loc] } for loc in sorted(matrix.keys())] return render( request, 'shipping/out-data.html', { 'apps': beta_apps, 'appvers': [avt.appversion for avt in avts], 'rows': rows, })
def changes(request, app_code): """Show which milestones on the given appversion took changes for which locale """ av = get_object_or_404(AppVersion, code=app_code) ms_names = {} ms_codes = {} for ms in (Milestone.objects .filter(appver=av) .select_related('appver__app')): ms_names[ms.id] = str(ms) ms_codes[ms.id] = ms.code rows = [] changes = None ms_id = None latest = {} current = {} ms_name = None # get historic data that enters this appversion # get that in fallback order, we'll reverse afterwards flags4av = flags4appversions([av]) flags4loc = flags4av[av] locs4av = defaultdict(dict) # av -> loc -> ACCEPTED notaccepted = {} # av -> flags for loc, (real_av, flags) in flags4loc.iteritems(): if Action.ACCEPTED in flags: locs4av[real_av][loc] = flags[Action.ACCEPTED] else: notaccepted[loc] = flags # for not accepted locales on the current appver, # check for accepted on fallbacks if av.fallback and notaccepted: flags4fallback = flags4av[av.fallback] for loc in notaccepted: if loc in flags4fallback: # if the loc isn't here, it's never been accepted so far real_av, flags = flags4fallback[loc] if Action.ACCEPTED in flags: locs4av[real_av] = flags[Action.ACCEPTED] # let's keep the current appver data around for later, # and order the fallbacks. # Also, keep track of how many locales fell back to # * the previous cycle # * the two cycles before that (2 and 3) # * older cycles (4 and more) accepted = locs4av.pop(av.code, {}) av_ = av fallback = 0 # how deep are we falling back buckets = {0: 0, 1: 1, 2: 2, 3: 2} # which fallback to which bucket bucket = 0 # bucket we're in rowspan = 0 # how many rows are in this bucket locales_group = set() # which locales are in this bucket while av_ and locs4av: thisbucket = buckets.get(fallback, 3) if thisbucket != bucket and locales_group: rows[-1].update({ 'rowspan': rowspan, 'group_locales_count': len(locales_group) }) locales_group.clear() rowspan = 0 bucket = thisbucket if av_.code in locs4av: accepted4loc = locs4av.pop(av_.code) # store actions for now, we'll get the push_ids later current.update(accepted4loc) locales_group.update(accepted4loc.keys()) rowspan += 1 rows.append({ 'name': str(av_), 'code': av_.code, 'isAppVersion': True, 'changes': [(loc, 'added') for loc in sorted(accepted4loc)] }) av_ = av_.fallback fallback += 1 if locales_group and rows: rows[-1].update({ 'rowspan': rowspan, 'group_locales_count': len(locales_group) }) rows.reverse() for loc, pid in (Signoff.objects .filter(action__in=current.values()) .values_list('locale__code', 'push__id')): current[loc] = pid for loc, pid in (Signoff.objects .filter(action__in=accepted.values()) .values_list('locale__code', 'push__id')): accepted[loc] = pid # reset group data for the current appversion, all one group avrow = None # keep track of the first row rowspan = 0 locales_group.clear() current = {} # we're only doing sign-offs for this appversion now, and for milestones # of this appversion for _mid, loc, pid in (Milestone_Signoffs.objects .filter(milestone__appver=av) .filter(signoff__appversion=av) .order_by('milestone__id', 'signoff__locale__code') .values_list('milestone__id', 'signoff__locale__code', 'signoff__push__id')): if _mid != ms_id: ms_id = _mid # next milestone, bootstrap new row if latest: # previous milestone has locales left, update previous changes changes += [(_loc, 'dropped') for _loc in latest.iterkeys()] changes.sort() latest = current current = {} ms_name = ms_names[ms_id] changes = [] rows.append({'name': ms_name, 'code': ms_codes[ms_id], 'changes': changes}) rowspan += 1 if avrow is None: avrow = rows[-1] if loc not in latest: changes.append((loc, 'added')) locales_group.add(loc) else: lpid = latest.pop(loc) if lpid != pid: changes.append((loc, 'changed')) current[loc] = pid # see if we have some locales dropped in the last milestone if latest: # previous milestone has locales left, update previous changes changes += [(loc, 'dropped') for loc in latest.iterkeys()] changes.sort() # add group info to the avrow if avrow: avrow.update({ 'rowspan': rowspan, 'rowspan_last': True, 'group_locales_count': len(locales_group) }) # finally, check if there's more signoffs after the last shipped milestone newso = [(loc, loc in current and 'changed' or 'added') for loc, pid in accepted.iteritems() if current.get(loc) != pid] for loc, flags in notaccepted.iteritems(): if Action.PENDING in flags: newso.append((loc, 'pending')) elif Action.REJECTED in flags: newso.append((loc, 'rejected')) elif Action.OBSOLETED in flags: newso.append((loc, 'obsoleted')) if newso: newso.sort() rows.append({ 'name': '%s .next' % str(av), 'changes': newso }) addcount = len([t for t in newso if t[1]=='added']) if addcount: rows[-1].update({ 'rowspan': 1, 'rowspan_last': True, 'group_locales_count': '+%d' % addcount }) return render(request, 'shipping/app-changes.html', { 'appver': av, 'rows': rows, })
def changes(request, app_code): """Show which milestones on the given appversion took changes for which locale """ av = get_object_or_404(AppVersion, code=app_code) ms_names = {} ms_codes = {} for ms in (Milestone.objects .filter(appver=av) .select_related('appver__app')): ms_names[ms.id] = str(ms) ms_codes[ms.id] = ms.code rows = [] changes = None ms_id = None latest = {} current = {} ms_name = None # get historic data that enters this appversion # get that in fallback order, we'll reverse afterwards flags4av = flags4appversions(appversions={"id": av.id}) flags4loc = flags4av[av] locs4av = defaultdict(dict) # av -> loc -> ACCEPTED notaccepted = {} # av -> flags for loc, (real_av, flags) in flags4loc.iteritems(): if Action.ACCEPTED in flags: locs4av[real_av][loc] = flags[Action.ACCEPTED] else: notaccepted[loc] = flags # for not accepted locales on the current appver, # check for accepted on fallbacks if av.fallback and notaccepted: flags4fallback = flags4av[av.fallback] for loc in notaccepted: if loc in flags4fallback: # if the loc isn't here, it's never been accepted so far real_av, flags = flags4fallback[loc] if Action.ACCEPTED in flags: locs4av[real_av] = flags[Action.ACCEPTED] # let's keep the current appver data around for later, # and order the fallbacks accepted = locs4av.pop(av.code, {}) av_ = av while av_ and locs4av: if av_.code in locs4av: accepted4loc = locs4av.pop(av_.code) # store actions for now, we'll get the push_ids later current.update(accepted4loc) rows.append({ 'name': str(av_), 'code': av_.code, 'isAppVersion': True, 'changes': [(loc, 'added') for loc in sorted(accepted4loc)] }) av_ = av_.fallback rows.reverse() for loc, pid in (Signoff.objects .filter(action__in=current.values()) .values_list('locale__code', 'push__id')): current[loc] = pid for loc, pid in (Signoff.objects .filter(action__in=accepted.values()) .values_list('locale__code', 'push__id')): accepted[loc] = pid for _mid, loc, pid in (Milestone_Signoffs.objects .filter(milestone__appver=av) .order_by('milestone__id', 'signoff__locale__code') .values_list('milestone__id', 'signoff__locale__code', 'signoff__push__id')): if _mid != ms_id: ms_id = _mid # next milestone, bootstrap new row if latest: # previous milestone has locales left, update previous changes changes += [(_loc, 'dropped') for _loc in latest.iterkeys()] changes.sort() latest = current current = {} ms_name = ms_names[ms_id] changes = [] rows.append({'name': ms_name, 'code': ms_codes[ms_id], 'changes': changes}) if loc not in latest: changes.append((loc, 'added')) else: lpid = latest.pop(loc) if lpid != pid: changes.append((loc, 'changed')) current[loc] = pid # see if we have some locales dropped in the last milestone if latest: # previous milestone has locales left, update previous changes changes += [(loc, 'dropped') for loc in latest.iterkeys()] changes.sort() # finally, check if there's more signoffs after the last shipped milestone newso = [(loc, loc in current and 'changed' or 'added') for loc, pid in accepted.iteritems() if current.get(loc) != pid] for loc, flags in notaccepted.iteritems(): if Action.PENDING in flags: newso.append((loc, 'pending')) elif Action.REJECTED in flags: newso.append((loc, 'rejected')) elif Action.OBSOLETED in flags: newso.append((loc, 'obsoleted')) if newso: newso.sort() rows.append({ 'name': '%s .next' % str(av), 'changes': newso }) return render(request, 'shipping/app-changes.html', { 'appver': av, 'rows': rows, })
def teamsnippet(loc, team_locales): locs = Locale.objects.filter(pk__in=[loc.pk] + list(team_locales)) runs = sorted( (Run.objects .filter(locale__in=locs, active__isnull=False) .select_related('tree', 'locale')), key=lambda r: (r.tree.code, '' if r.locale.code == loc.code else r.locale.code) ) # this locale isn't active in our trees yet if not runs: return '' # Create these two based on all appversions attached to a tree so that in # the big loop on runs we don't need to make excessive queries for # appversions and applications _trees_to_appversions = {} for avt in (AppVersion.trees.through.objects .current() .select_related('appversion__app', 'tree')): _trees_to_appversions[avt.tree] = avt.appversion def tree_to_appversion(tree): return _trees_to_appversions.get(tree) def tree_to_application(tree): av = tree_to_appversion(tree) return av and av.app or None # find good revisions to sign-off, latest run needs to be green. # keep in sync with api.annotated_pushes suggested_runs = filter( lambda r: r.allmissing == 0 and r.errors == 0, runs ) suggested_rev = dict(Run_Revisions.objects .filter(run__in=suggested_runs, changeset__repositories__locale__in=locs) .values_list('run_id', 'changeset__revision')) progresses = dict( ((pp.tree.code, pp.locale.code), pp) for pp in ( ProgressPosition.objects .filter(tree__run__in=runs) .select_related('tree', 'locale') ) ) applications = defaultdict(list) pushes = set() for run_ in runs: # copy the Run instance into a fancy dict but only copy those whose # key doesn't start with an underscore run = RunElement(dict((k, getattr(run_, k)) for k in run_.__dict__ if not k.startswith('_'))) run.locale = run_.locale run.allmissing = run_.allmissing # a @property of the Run model run.tree = run_.tree # foreign key lookup application = tree_to_application(run_.tree) run.changed_ratio = run.completion run.unchanged_ratio = 100 * run.unchanged / run.total run.missing_ratio = 100 * run.allmissing / run.total # cheat a bit and make sure that the red stripe on the chart is at # least 1px wide if run.allmissing and run.missing_ratio == 0: run.missing_ratio = 1 for ratio in (run.changed_ratio, run.unchanged_ratio): if ratio: ratio -= 1 break run.prog_pos = progresses.get((run.tree.code, run.locale.code)) appversion = tree_to_appversion(run.tree) # because Django templates (stupidly) swallows lookup errors we # have to apply the missing defaults too run.pending = run.rejected = run.accepted = \ run.suggested_shortrev = \ run.fallback = run.appversion = None if appversion and appversion.accepts_signoffs: run.appversion = appversion real_av, flags = ( flags4appversions( locales={'id': run_.locale.id}, appversions={'id': appversion.id} ) .get(appversion, {}) .get(run_.locale.code, [None, {}]) ) # get current status of signoffs # we really only need the shortrevs, we'll get those below if flags: actions = Action.objects.filter(id__in=flags.values()) \ .select_related('signoff__push') action4id = dict((a.id, a) for a in actions) for flag in (Action.PENDING, Action.ACCEPTED, Action.REJECTED): if flag in flags: a = action4id[flags[flag]] setattr(run, a.get_flag_display(), a.signoff.push) if appversion.code != real_av: run.fallback = real_av pushes.update((run.pending, run.rejected, run.accepted)) # get the suggested signoff. If there are existing actions # we'll unset it when we get the shortrevs for those below if run_.id in suggested_rev: run.suggested_shortrev = suggested_rev[run_.id][:12] applications[application].append(run) # get the tip shortrevs for all our pushes pushes = map(lambda p: p.id, filter(None, pushes)) tip4push = dict(Push.objects .annotate(tc=Max('changesets')) .filter(id__in=pushes) .values_list('id', 'tc')) rev4id = dict(Changeset.objects .filter(id__in=tip4push.values()) .values_list('id', 'revision')) for runs in applications.itervalues(): for run in runs: for k in ('pending', 'rejected', 'accepted'): if run[k] is not None: run[k + '_rev'] = rev4id[tip4push[run[k].id]][:12] # unset the suggestion if there's existing signoff action if run[k + '_rev'] == run.suggested_shortrev: run.suggested_shortrev = None applications = sorted( ((k, v) for (k, v) in applications.items()), key=lambda t: t[0] and t[0].name or None ) other_team_locales = Locale.objects.filter(id__in=team_locales) progress_start = datetime.utcnow() - timedelta(days=settings.PROGRESS_DAYS) return render_to_string('shipping/team-snippet.html', {'locale': loc, 'other_team_locales': other_team_locales, 'applications': applications, 'progress_start': progress_start, })
def changes(request, app_code): """Show fallbacks for a given appversion. """ av = get_object_or_404(AppVersion, code=app_code) rows = [] changes = None latest = {} current = {} # get historic data that enters this appversion # get that in fallback order, we'll reverse afterwards flags4av = flags4appversions([av]) flags4loc = flags4av[av] locs4av = defaultdict(dict) # av -> loc -> ACCEPTED notaccepted = {} # av -> flags for loc, (real_av, flags) in flags4loc.iteritems(): if Action.ACCEPTED in flags: locs4av[real_av][loc] = flags[Action.ACCEPTED] else: notaccepted[loc] = flags # for not accepted locales on the current appver, # check for accepted on fallbacks if av.fallback and notaccepted: flags4fallback = flags4av[av.fallback] for loc in notaccepted: if loc in flags4fallback: # if the loc isn't here, it's never been accepted so far real_av, flags = flags4fallback[loc] if Action.ACCEPTED in flags: locs4av[real_av] = flags[Action.ACCEPTED] # let's keep the current appver data around for later, # and order the fallbacks. # Also, keep track of how many locales fell back to # * the previous cycle # * the two cycles before that (2 and 3) # * older cycles (4 and more) accepted = locs4av.pop(av.code, {}) av_ = av fallback = 0 # how deep are we falling back buckets = {0: 0, 1: 1, 2: 2, 3: 2} # which fallback to which bucket bucket = 0 # bucket we're in rowspan = 0 # how many rows are in this bucket locales_group = set() # which locales are in this bucket while av_ and locs4av: thisbucket = buckets.get(fallback, 3) if thisbucket != bucket and locales_group: rows[-1].update({ 'rowspan': rowspan, 'group_locales_count': len(locales_group) }) locales_group.clear() rowspan = 0 bucket = thisbucket if av_.code in locs4av: accepted4loc = locs4av.pop(av_.code) # store actions for now, we'll get the push_ids later current.update(accepted4loc) locales_group.update(accepted4loc.keys()) rowspan += 1 rows.append({ 'name': str(av_), 'code': av_.code, 'isAppVersion': True, 'changes': [(loc, 'added') for loc in sorted(accepted4loc)] }) av_ = av_.fallback fallback += 1 if locales_group and rows: rows[-1].update({ 'rowspan': rowspan, 'group_locales_count': len(locales_group) }) rows.reverse() for loc, pid in (Signoff.objects.filter( action__in=current.values()).values_list('locale__code', 'push__id')): current[loc] = pid for loc, pid in (Signoff.objects.filter( action__in=accepted.values()).values_list('locale__code', 'push__id')): accepted[loc] = pid # reset group data for the current appversion, all one group avrow = None # keep track of the first row rowspan = 0 locales_group.clear() # see if we have some locales dropped in the last appver if latest: # previous appver has locales left, update previous changes changes += [(loc, 'dropped') for loc in latest.iterkeys()] changes.sort() # add group info to the avrow if avrow: avrow.update({ 'rowspan': rowspan, 'rowspan_last': True, 'group_locales_count': len(locales_group) }) # finally, check if there's more signoffs after the last shipped appver newso = [(loc, loc in current and 'changed' or 'added') for loc, pid in accepted.iteritems() if current.get(loc) != pid] for loc, flags in notaccepted.iteritems(): if Action.PENDING in flags: newso.append((loc, 'pending')) elif Action.REJECTED in flags: newso.append((loc, 'rejected')) elif Action.OBSOLETED in flags: newso.append((loc, 'obsoleted')) if newso: newso.sort() rows.append({'name': '%s .next' % str(av), 'changes': newso}) addcount = len([t for t in newso if t[1] == 'added']) if addcount: rows[-1].update({ 'rowspan': 1, 'rowspan_last': True, 'group_locales_count': '+%d' % addcount }) return render(request, 'shipping/app-changes.html', { 'appver': av, 'rows': rows, })
def _get_flags_and_actions(self): __, flags = (api.flags4appversions( locales=self._locale_search, appversions=self._appver_search).get( self.av, {}).get(self.locale.code, [None, {}])) actions = Action.objects.filter(id__in=flags.values()) return flags, actions
def get_signoffs(self): appversions_with_pushes = [] _treeid_to_avt = {} tree_ids = {} avts = (AppVersion.trees.through.objects .current() .filter(appversion__accepts_signoffs=True) ) if self.avs: avts = avts.filter(appversion__code__in=self.avs) for avt in avts.select_related('appversion__app', 'tree__l10n'): _treeid_to_avt[avt.tree.id] = avt tree_ids[avt.tree.code] = avt.tree.id appvers = [avt.appversion for avt in _treeid_to_avt.values()] locales = None if self.locales: locales = list(Locale.objects .filter(code__in=self.locales) .values_list('id', flat=True)) locflags4av = flags4appversions(appvers, locales=locales) av2tree = dict((avt.appversion.code, avt.tree.code) for avt in _treeid_to_avt.itervalues()) values = dict(Action._meta.get_field('flag').flatchoices) so_items = {} actions = {} for av in appvers: for loc, (real_av, flags) in locflags4av[av].iteritems(): flag_values = [ (real_av == av.code or f != Action.ACCEPTED) and values[f] or real_av for f in flags] item = { 'flags': flag_values, 'state': None, 'action': [] } if Action.ACCEPTED in flags: item['state'] = (real_av == av.code) and 'OK' or real_av if Action.REJECTED in flags: item['action'].append('rejected') if Action.PENDING in flags: item['action'].append('review') so_items[(av2tree[av.code], loc)] = item for flag, action in flags.items(): if real_av == av.code or flag == Action.PENDING: actions[action] = [av, loc] for action, signoff, push_date in (Action.objects .filter(id__in = actions.keys()) .values_list('id', 'signoff', 'signoff__push__push_date')): actions[action] += [signoff, push_date] last_action = {} last_signoff = {} for action, (av, loc, signoff, push_date) in sorted( actions.iteritems(), key=lambda t: t[1][3], reverse=True): if (av, loc) in last_action: continue last_action[(av, loc)] = push_date last_signoff[(av, loc)] = signoff runs_with_open_av = [run for run, tree in self.runids2tree.items() if tree in tree_ids and _treeid_to_avt[tree_ids[tree]].appversion.accepts_signoffs] changesets = dict((tuple(t[:2]), t[2]) for t in Run.revisions.through.objects .filter(run__in=runs_with_open_av, changeset__repositories__locale__isnull=False) .values_list('run__tree', 'run__locale__code', 'changeset')) pushdates = dict((tuple(t[:2]), t[2]) for t in Push.changesets.through.objects .filter(changeset__in=set(changesets.values())) .values_list('changeset', 'push__repository__forest', 'push__push_date')) avl_has_new_run = set() maybe_new_run = [] for (treeid, locale_code), changeset in changesets.iteritems(): avt = _treeid_to_avt[treeid] push_date = pushdates[(changeset, avt.tree.l10n.id)] if avt.start and push_date < avt.start: # need update appversions_with_pushes.append({ "needs_update": True, "type": "NewPush", "label": avt.tree.code + '/' + locale_code }) continue if avt.end and push_date > avt.end: continue if ((avt.appversion, locale_code) in last_action and last_action[(avt.appversion, locale_code)] >= push_date): continue maybe_new_run.append((avt.appversion, changeset, avt.tree.code, locale_code, last_signoff.get((avt.appversion, locale_code)) )) # find out if the sign-off is on the same revision as the last push old_signoffs = filter(None, (t[4] for t in maybe_new_run)) signoffs_to_skip = set() if old_signoffs: so_tips = dict(Signoff.objects .filter(id__in=old_signoffs) .annotate(cs=Max("push__changesets")) .values_list('id', 'cs')) latest_changeset = dict((t[4], t[1]) for t in maybe_new_run) for so_id, tip in so_tips.iteritems(): if latest_changeset[so_id] == tip: signoffs_to_skip.add(so_id) for av, changeset, tree_code, locale_code, last_so in maybe_new_run: if last_so in signoffs_to_skip: continue avl_has_new_run.add((tree_code, locale_code)) appversions_with_pushes.append({ "new_run": "sign off", "type": "NewPush", "label": tree_code + '/' + locale_code }) # make a list now items = [] for (tree, locale), values in sorted(so_items.iteritems(), key=lambda t:t[0]): glyph = '' if values['state'] == 'OK': glyph = 'check' if (tree, locale) in avl_has_new_run: glyph = 'graph' elif values['state']: glyph = 'warning' item = { "type": "SignOff", "label": "%s/%s" % (tree, locale), "tree": tree, "state": values['state'], "state_glyph": glyph, "signoff": sorted(values['flags']) } if values['action']: item['action'] = values['action'] items.append(item) items += [{"type": "AppVer4Tree", "label": tree, "appversion": av} for av, tree in av2tree.iteritems()] items += appversions_with_pushes return items
def data(request): app_codes = request.GET.getlist('app') if not app_codes: raise Http404('List of applications required') beta_apps = Application.objects.filter(code__in=app_codes) if len(app_codes) != beta_apps.count(): raise Http404('Some of the given apps were not found') branches = request.GET.getlist('branch') f_aurora = Forest.objects.get(name='releases/l10n/mozilla-aurora') f_beta = Forest.objects.get(name='releases/l10n/mozilla-beta') forests = [] if 'aurora' in branches: forests.append(f_aurora) if 'beta' in branches: forests.append(f_beta) name4app = dict(beta_apps.values_list('id', 'name')) # get AppVersion_Trees avts = (AppVersionTreeThrough.objects.current() .filter(appversion__app__in=name4app.keys(), tree__l10n__in=forests) .order_by('appversion__code') .select_related('appversion__app', 'tree')) avts = list(avts) code4av = dict((avt.appversion_id, avt.appversion.code) for avt in avts) appname4av = dict((avt.appversion_id, name4app[avt.appversion.app_id]) for avt in avts) tree4av = dict((avt.appversion_id, avt.tree_id) for avt in avts) av4tree = dict((avt.tree_id, avt.appversion_id) for avt in avts) locs = set() loc4tree = defaultdict(list) for t, l in (Run.objects .filter(tree__in=tree4av.values(), active__isnull=False) .values_list('tree', 'locale__code') .distinct()): loc4tree[t].append(l) locs.add(l) locflags4av = flags4appversions([avt.appversion for avt in avts]) loc4tree = defaultdict(list) for av, flags4loc in locflags4av.iteritems(): if av.id not in tree4av: continue for loc, (real_av, flags) in flags4loc.iteritems(): if real_av == av.code: continue loc4tree[tree4av[av.id]].append(loc) loc4tree = dict((t, locs) for t, locs in loc4tree.iteritems() if locs) or_ = lambda l, r: l | r runqueries = reduce(or_, (reduce(or_, (Q(tree=t, locale__code=l) for l in locs)) for t, locs in loc4tree.iteritems())) actives = (Run.objects .filter(runqueries) .exclude(active__isnull=True) .select_related('locale')) missings = dict(((r.tree_id, r.locale.code), r.allmissing) for r in actives) matrix = dict() columns = tuple(avt.appversion_id for avt in avts) for tree_id, locs in loc4tree.iteritems(): av_id = av4tree[tree_id] for loc in locs: if loc not in matrix: matrix[loc] = [None] * len(avts) col_index = columns.index(av_id) entry = {'av': code4av[av_id], 'app': appname4av[av_id], 'missing': missings[(tree4av[av_id], loc)]} matrix[loc][col_index] = entry rows = [{'loc':loc, 'entries': matrix[loc]} for loc in sorted(matrix.keys())] return render(request, 'shipping/out-data.html', { 'apps': beta_apps, 'appvers': [avt.appversion for avt in avts], 'rows': rows, })