def test_pending(self): """Test for the pending polish signoff""" actions = signoff_actions(appversions={"code": "fx1.0"}, locales={"code": "pl"}) actions = list(actions) eq_(len(actions), 1) so = Signoff.objects.get(action=actions[0][0]) eq_(so.push.tip.shortrev, "l10n pl 0003") eq_(so.locale.code, "pl") eq_(so.action_set.count(), 1)
def test_accepted(self): """Test for the german accepted signoff""" actions = signoff_actions(appversions={"code": "fx1.0"}, locales={"code": "de"}) actions = list(actions) eq_(len(actions), 1) so = Signoff.objects.get(action=actions[0][0]) eq_(so.push.tip.shortrev, "l10n de 0002") eq_(so.locale.code, "de") eq_(so.action_set.count(), 2)
def test_rejected(self): """Test for the rejected polish signoff""" actions = signoff_actions(appversions={"code": "fx1.0"}, locales={"code": "fr"}) actions = list(actions) eq_(len(actions), 1) eq_(actions[0][1], Action.REJECTED) so = Signoff.objects.get(action=actions[0][0]) eq_(so.push.tip.shortrev, "l10n fr 0003") eq_(so.locale.code, "fr") eq_(so.action_set.count(), 2)
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) forest = appver.tree.l10n repo = get_object_or_404(Repository, locale=lang, forest=forest) # which pushes to show actions = list(a_id for a_id, flag in \ signoff_actions(appversions={'id': appver.id}, locales={'id': lang.id})) actions = list(Action.objects.filter(id__in=actions) .select_related('signoff__push', 'author')) # get current status of signoffs pending, rejected, accepted, initial_diff = signoff_summary(actions) pushes, currentpush, suggested_signoff = annotated_pushes(repo, appver, lang, actions, initial_diff) return render(request, 'shipping/signoffs.html', { 'appver': appver, 'language': lang, 'pushes': pushes, 'current': currentpush, 'pending': pending, 'rejected': rejected, 'accepted': accepted, 'tree': appver.tree.code, 'repo': repo, 'suggested_signoff': suggested_signoff, 'login_form_needs_reload': True, 'request': request, })
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') try: auroradate = datetime.datetime.strptime(request.GET.get('auroradate'), _dateformat) except (ValueError, TypeError): raise Http404("missing auroradate, or doesn't match %s" % _dateformat) try: betadate = datetime.datetime.strptime(request.GET.get('betadate'), _dateformat) except (ValueError, TypeError): raise Http404("missing betadate, or doesn't match %s" % _dateformat) 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) appvers = (AppVersion.objects.filter( app__in=list(beta_apps.values_list('id', flat=True)), tree__l10n__in=forests).order_by('code').select_related('app', 'tree')) appvers = list(appvers) beta_av = dict.fromkeys(av.id for av in appvers if av.tree.l10n_id == f_beta.id) name4app = dict(beta_apps.values_list('id', 'name')) code4av = dict((av.id, av.code) for av in appvers) appname4av = dict((av.id, name4app[av.app_id]) for av in appvers) tree4av = dict((av.id, av.tree_id) for av in appvers) avq = {'id__in': [av.id for av in appvers]} actions = [ action_id for action_id, flag in signoff_actions(appversions=avq) if flag == Action.ACCEPTED ] old_signoffs = list( Signoff.objects.filter(action__in=actions).exclude( push__push_date__gte=auroradate).select_related( 'push__repository')) # exclude signoffs on aurora pushes in the previous cycle # that are now on beta old_signoffs = filter( lambda so: not (so.push.repository.forest == f_aurora and so. appversion_id in beta_av and so.push.push_date >= betadate and so.push.push_date < auroradate), old_signoffs) runqueries = (Q(locale=so.locale_id, tree=tree4av[so.appversion_id]) for so in old_signoffs) actives = (Run.objects.filter(reduce( lambda l, r: l | r, runqueries)).exclude(active__isnull=True)) missings = dict(((r.tree_id, r.locale_id), r.allmissing) for r in actives) matrix = dict() for signoff in old_signoffs: if signoff.locale_id not in matrix: matrix[signoff.locale_id] = [None] * len(appvers) for av_i, av in enumerate(appvers): if signoff.appversion_id == av.id: break entry = { 'push': signoff.push.push_date, 'av': code4av[signoff.appversion_id], 'app': appname4av[signoff.appversion_id], 'missing': missings[(tree4av[signoff.appversion_id], signoff.locale_id)] } matrix[signoff.locale_id][av_i] = entry id4loc = dict( (Locale.objects.filter(id__in=matrix.keys()).values_list('code', 'id'))) rows = [{ 'loc': loc, 'entries': matrix[id4loc[loc]] } for loc in sorted(id4loc.keys())] return render_to_response('shipping/out-data.html', { 'apps': beta_apps, 'appvers': appvers, 'rows': rows, 'auroradate': auroradate.strftime(_dateformat), 'betadate': betadate.strftime(_dateformat), }, context_instance=RequestContext(request))
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') try: auroradate = datetime.datetime.strptime(request.GET.get('auroradate'), _dateformat) except (ValueError, TypeError): raise Http404("missing auroradate, or doesn't match %s" % _dateformat) try: betadate = datetime.datetime.strptime(request.GET.get('betadate'), _dateformat) except (ValueError, TypeError): raise Http404("missing betadate, or doesn't match %s" % _dateformat) 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) appvers = (AppVersion.objects. filter(app__in=list(beta_apps.values_list('id', flat=True)), tree__l10n__in=forests) .order_by('code') .select_related('app', 'tree')) appvers = list(appvers) beta_av = dict.fromkeys(av.id for av in appvers if av.tree.l10n_id == f_beta.id) name4app = dict(beta_apps.values_list('id', 'name')) code4av = dict((av.id, av.code) for av in appvers) appname4av = dict((av.id, name4app[av.app_id]) for av in appvers) tree4av = dict((av.id, av.tree_id) for av in appvers) avq = {'id__in': [av.id for av in appvers]} actions = [action_id for action_id, flag in signoff_actions(appversions=avq) if flag == Action.ACCEPTED] old_signoffs = list(Signoff.objects .filter(action__in=actions) .exclude(push__push_date__gte=auroradate) .select_related('push__repository')) # exclude signoffs on aurora pushes in the previous cycle # that are now on beta old_signoffs = filter(lambda so: not (so.push.repository.forest == f_aurora and so.appversion_id in beta_av and so.push.push_date >= betadate and so.push.push_date < auroradate), old_signoffs) runqueries = (Q(locale=so.locale_id, tree=tree4av[so.appversion_id]) for so in old_signoffs) actives = (Run.objects .filter(reduce(lambda l, r: l | r, runqueries)) .exclude(active__isnull=True)) missings = dict(((r.tree_id, r.locale_id), r.allmissing) for r in actives) matrix = dict() for signoff in old_signoffs: if signoff.locale_id not in matrix: matrix[signoff.locale_id] = [None] * len(appvers) for av_i, av in enumerate(appvers): if signoff.appversion_id == av.id: break entry = {'push': signoff.push.push_date, 'av': code4av[signoff.appversion_id], 'app': appname4av[signoff.appversion_id], 'missing': missings[(tree4av[signoff.appversion_id], signoff.locale_id)]} matrix[signoff.locale_id][av_i] = entry id4loc = dict((Locale.objects .filter(id__in=matrix.keys()) .values_list('code', 'id'))) rows = [{'loc':loc, 'entries': matrix[id4loc[loc]]} for loc in sorted(id4loc.keys())] return render_to_response('shipping/out-data.html', {'apps': beta_apps, 'appvers': appvers, 'rows': rows, 'auroradate': auroradate.strftime(_dateformat), 'betadate': betadate.strftime(_dateformat), }, context_instance=RequestContext(request))
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 appver in (AppVersion.objects .exclude(tree__isnull=True) .select_related('tree')): _trees_to_appversions[appver.tree] = appver 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.appversion = None if appversion: run.appversion = appversion actions = [action_id for action_id, flag in signoff_actions( appversions=[run.appversion], locales=[loc.id]) ] actions = Action.objects.filter(id__in=actions) \ .select_related('signoff__push') # get current status of signoffs # we really only need the shortrevs, we'll get those below run.pending, run.rejected, run.accepted, __ = \ signoff_summary(actions) 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 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) forest = appver.tree.l10n repo = get_object_or_404(Repository, locale=lang, forest=forest) # which pushes to show pushes_q = Push.objects.order_by('-push_date').filter( changesets__branch__id=1) pushes_q = pushes_q.filter(repository=repo) actions = list(a_id for a_id, flag in \ signoff_actions(appversions={'id': appver.id}, locales={'id': lang.id})) actions = list( Action.objects.filter(id__in=actions).select_related( 'signoff__push', 'author')) current_so = currentpush = None actions4push = defaultdict(list) for action in actions: if action.flag == Action.ACCEPTED: current_so = action.signoff currentpush = current_so.push_id actions4push[action.signoff.push_id].append(action) if current_so is not None: pushes_q = pushes_q.filter( push_date__gte=current_so.push.push_date).distinct() else: pushes_q = pushes_q.distinct()[:10] # get pushes, changesets and signoffs/actions _p = list(pushes_q.values_list('id', flat=True)) pcs = Push_Changesets.objects.filter(push__in=_p).order_by( '-push__push_date', '-changeset__id') pushes = _RowCollector(pcs, actions4push).pushes # get current status of signoffs pending = rejected = accepted = None all_actions = sorted(actions, key=lambda _a: -_a.signoff.id) initial_diff = [] for action in all_actions: flag = action.flag _so = action.signoff if flag == Action.PENDING: # keep if there's no pending or rejected if pending is None and rejected is None: pending = _so.push if len(initial_diff) < 2: initial_diff.append(_so.id) continue elif flag == Action.ACCEPTED: # store and don't look any further accepted = _so.push if len(initial_diff) < 2: initial_diff.append(_so.id) break elif flag == Action.REJECTED: # keep, if there's no rejected if rejected is None: rejected = _so.push if len(initial_diff) < 2: initial_diff.append(_so.id) continue elif flag == Action.OBSOLETED: # obsoleted, stop looking break else: # flag == Action.CANCELED, ignore, keep looking pass # get latest runs for our changesets csl = list(pcs.values_list('changeset__id', flat=True)) rrs = Run_Revisions.objects.filter(run__tree=appver.tree_id, run__locale=lang, changeset__in=csl) rrs = rrs.order_by('changeset', 'run') c2r = dict(rrs.values_list('changeset', 'run')) r2r = dict((r.id, r) for r in (Run.objects.filter( id__in=c2r.values()).select_related('build'))) # merge data back into pushes list suggested_signoff = None # initial_diff and runs if len(initial_diff) < 2 and pushes: pushes[0]['changes'][0].diffbases = [None] * (2 - len(initial_diff)) for p in pushes: # initial_diff for sod in p['signoffs']: if sod['signoff'].id in initial_diff: sod['diffbases'] = 1 # runs for c in p['changes']: if c.id in c2r and c2r[c.id] is not None: # we stored a run for a changeset in this push _r = r2r[c2r[c.id]] p['run'] = _r # should we suggest this? if suggested_signoff is None: if p['signoffs']: # last good push is signed off, don't suggest anything suggested_signoff = False elif _r.allmissing == 0 and _r.errors == 0: # source checks are good, suggest suggested_signoff = p['id'] return render_to_response('shipping/signoffs.html', { 'appver': appver, 'language': lang, 'pushes': pushes, 'current': currentpush, 'pending': pending, 'rejected': rejected, 'accepted': accepted, 'tree': appver.tree.code, 'repo': repo, 'suggested_signoff': suggested_signoff, 'login_form_needs_reload': True, 'request': request, }, context_instance=RequestContext(request))
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) forest = appver.tree.l10n repo = get_object_or_404(Repository, locale=lang, forest=forest) # which pushes to show pushes_q = Push.objects.order_by('-push_date').filter(changesets__branch__id=1) pushes_q = pushes_q.filter(repository=repo) actions = list(a_id for a_id, flag in \ signoff_actions(appversions={'id': appver.id}, locales={'id': lang.id})) actions = list(Action.objects.filter(id__in=actions) .select_related('signoff__push', 'author')) current_so = currentpush = None actions4push = defaultdict(list) for action in actions: if action.flag == Action.ACCEPTED: current_so = action.signoff currentpush = current_so.push_id actions4push[action.signoff.push_id].append(action) if current_so is not None: pushes_q = pushes_q.filter(push_date__gte=current_so.push.push_date).distinct() else: pushes_q = pushes_q.distinct()[:10] # get pushes, changesets and signoffs/actions _p = list(pushes_q.values_list('id',flat=True)) pcs = Push_Changesets.objects.filter(push__in=_p).order_by('-push__push_date','-changeset__id') pushes = _RowCollector(pcs, actions4push).pushes # get current status of signoffs pending = rejected = accepted = None all_actions = sorted(actions, key=lambda _a: -_a.signoff.id) initial_diff = [] for action in all_actions: flag = action.flag _so = action.signoff if flag == Action.PENDING: # keep if there's no pending or rejected if pending is None and rejected is None: pending = _so.push if len(initial_diff) < 2:initial_diff.append(_so.id) continue elif flag == Action.ACCEPTED: # store and don't look any further accepted = _so.push if len(initial_diff) < 2:initial_diff.append(_so.id) break elif flag == Action.REJECTED: # keep, if there's no rejected if rejected is None: rejected = _so.push if len(initial_diff) < 2:initial_diff.append(_so.id) continue elif flag == Action.OBSOLETED: # obsoleted, stop looking break else: # flag == Action.CANCELED, ignore, keep looking pass # get latest runs for our changesets csl = list(pcs.values_list('changeset__id', flat=True)) rrs = Run_Revisions.objects.filter(run__tree=appver.tree_id, run__locale=lang, changeset__in=csl) rrs = rrs.order_by('changeset', 'run') c2r = dict(rrs.values_list('changeset', 'run')) r2r = dict((r.id, r) for r in (Run.objects .filter(id__in=c2r.values()) .select_related('build'))) # merge data back into pushes list suggested_signoff = None # initial_diff and runs if len(initial_diff) < 2 and pushes: pushes[0]['changes'][0].diffbases = [None] * (2 - len(initial_diff)) for p in pushes: # initial_diff for sod in p['signoffs']: if sod['signoff'].id in initial_diff: sod['diffbases'] = 1 # runs for c in p['changes']: if c.id in c2r and c2r[c.id] is not None: # we stored a run for a changeset in this push _r = r2r[c2r[c.id]] p['run'] = _r # should we suggest this? if suggested_signoff is None: if p['signoffs']: # last good push is signed off, don't suggest anything suggested_signoff = False elif _r.allmissing == 0 and _r.errors == 0: # source checks are good, suggest suggested_signoff = p['id'] return render_to_response('shipping/signoffs.html', {'appver': appver, 'language': lang, 'pushes': pushes, 'current': currentpush, 'pending': pending, 'rejected': rejected, 'accepted': accepted, 'tree': appver.tree.code, 'repo': repo, 'suggested_signoff': suggested_signoff, 'login_form_needs_reload': True, 'request': request, }, context_instance=RequestContext(request))