Refactor internal stuff into new internals/ directory. (#1235)
This commit is contained in:
Родитель
b97a84007c
Коммит
891e84d7a3
|
@ -20,7 +20,7 @@ import logging
|
|||
|
||||
from framework import basehandlers
|
||||
import models
|
||||
import notifier
|
||||
from internals import notifier
|
||||
|
||||
|
||||
class StarsAPI(basehandlers.APIHandler):
|
||||
|
|
|
@ -25,7 +25,7 @@ import werkzeug.exceptions # Flask HTTP stuff.
|
|||
from api import register
|
||||
from api import stars_api
|
||||
import models
|
||||
import notifier
|
||||
from internals import notifier
|
||||
|
||||
|
||||
|
||||
|
|
8
app.yaml
8
app.yaml
|
@ -36,15 +36,15 @@ handlers:
|
|||
|
||||
# Admin ------------------------------------------------------------------------
|
||||
- url: /cron/.*
|
||||
script: admin.app
|
||||
script: internals.fetchmetrics.app
|
||||
# Any cron job must be harmless if it is called too often or with bad args
|
||||
|
||||
- url: /tasks/.*
|
||||
script: notifier.app
|
||||
script: internals.notifier.app
|
||||
# Header checks prevent raw access to this handler. Tasks have headers.
|
||||
|
||||
- url: /_ah/bounce
|
||||
script: notifier.app
|
||||
script: internals.notifier.app
|
||||
login: admin # Prevents raw access to this handler.
|
||||
|
||||
- url: /admin/blink.*
|
||||
|
@ -58,7 +58,7 @@ handlers:
|
|||
secure: always
|
||||
|
||||
- url: /admin/features/.*
|
||||
script: admin.app
|
||||
script: pages.intentpreview.app
|
||||
login: required # non-admin
|
||||
secure: always
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Google Inc.
|
||||
# Copyright 2021 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License")
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -16,12 +13,15 @@ from __future__ import print_function
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'ericbidelman@chromium.org (Eric Bidelman)'
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from framework import ramcache
|
||||
import requests
|
||||
# Note: this file cannot import models because it would be circular.
|
||||
|
||||
|
||||
def get_omaha_data():
|
||||
omaha_data = ramcache.get('omaha_data')
|
|
@ -1,8 +1,5 @@
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2013 Google Inc.
|
||||
# Copyright 2021 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License")
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -16,28 +13,35 @@ from __future__ import print_function
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'ericbidelman@chromium.org (Eric Bidelman)'
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from xml.dom import minidom
|
||||
|
||||
# Appengine imports.
|
||||
from framework import ramcache
|
||||
import requests
|
||||
from google.appengine.api import users
|
||||
from google.appengine.ext import db
|
||||
|
||||
# File imports.
|
||||
from framework import basehandlers
|
||||
from framework import utils
|
||||
import models
|
||||
import processes
|
||||
import settings
|
||||
|
||||
|
||||
def get_omaha_data():
|
||||
omaha_data = ramcache.get('omaha_data')
|
||||
if omaha_data is None:
|
||||
result = requests.get('https://omahaproxy.appspot.com/all.json')
|
||||
if result.status_code == 200:
|
||||
omaha_data = json.loads(result.content)
|
||||
ramcache.set('omaha_data', omaha_data, time=86400) # cache for 24hrs.
|
||||
return omaha_data
|
||||
|
||||
|
||||
UMA_QUERY_SERVER = 'https://uma-export.appspot.com/chromestatus/'
|
||||
|
||||
HISTOGRAMS_URL = 'https://chromium.googlesource.com/chromium/src/+/master/' \
|
||||
|
@ -257,9 +261,12 @@ class HistogramsHandler(basehandlers.FlaskHandler):
|
|||
|
||||
enum_tags = dom.getElementsByTagName('enum')
|
||||
|
||||
# Save bucket ids for each histogram type, FeatureObserver and MappedCSSProperties.
|
||||
# Save bucket ids for each histogram type, FeatureObserver and
|
||||
# MappedCSSProperties.
|
||||
for histogram_id in self.MODEL_CLASS.keys():
|
||||
enum = filter(lambda enum: enum.attributes['name'].value == histogram_id, enum_tags)[0]
|
||||
enum = filter(
|
||||
lambda enum: enum.attributes['name'].value == histogram_id,
|
||||
enum_tags)[0]
|
||||
for child in enum.getElementsByTagName('int'):
|
||||
self._SaveData({
|
||||
'bucket_id': child.attributes['value'].value,
|
||||
|
@ -269,77 +276,6 @@ class HistogramsHandler(basehandlers.FlaskHandler):
|
|||
return 'Success'
|
||||
|
||||
|
||||
|
||||
INTENT_PARAM = 'intent'
|
||||
LAUNCH_PARAM = 'launch'
|
||||
VIEW_FEATURE_URL = '/feature'
|
||||
|
||||
|
||||
class IntentEmailPreviewHandler(basehandlers.FlaskHandler):
|
||||
"""Show a preview of an intent email, as appropriate to the feature stage."""
|
||||
|
||||
TEMPLATE_PATH = 'admin/features/launch.html'
|
||||
|
||||
def get_template_data(self, feature_id=None, stage_id=None):
|
||||
user = users.get_current_user()
|
||||
if user is None:
|
||||
return self.redirect(users.create_login_url(self.request.path))
|
||||
|
||||
if not feature_id:
|
||||
self.abort(404)
|
||||
f = models.Feature.get_by_id(feature_id)
|
||||
if f is None:
|
||||
self.abort(404)
|
||||
|
||||
intent_stage = stage_id if stage_id is not None else f.intent_stage
|
||||
|
||||
if not self.user_can_edit(user):
|
||||
self.abort(403)
|
||||
|
||||
page_data = self.get_page_data(feature_id, f, intent_stage)
|
||||
return page_data
|
||||
|
||||
def get_page_data(self, feature_id, f, intent_stage):
|
||||
"""Return a dictionary of data used to render the page."""
|
||||
page_data = {
|
||||
'subject_prefix': self.compute_subject_prefix(f, intent_stage),
|
||||
'feature': f.format_for_template(),
|
||||
'sections_to_show': processes.INTENT_EMAIL_SECTIONS.get(
|
||||
intent_stage, []),
|
||||
'intent_stage': intent_stage,
|
||||
'default_url': '%s://%s%s/%s' % (
|
||||
self.request.scheme, self.request.host,
|
||||
VIEW_FEATURE_URL, feature_id),
|
||||
}
|
||||
|
||||
if LAUNCH_PARAM in self.request.args:
|
||||
page_data[LAUNCH_PARAM] = True
|
||||
if INTENT_PARAM in self.request.args:
|
||||
page_data[INTENT_PARAM] = True
|
||||
|
||||
return page_data
|
||||
|
||||
def compute_subject_prefix(self, feature, intent_stage):
|
||||
"""Return part of the subject line for an intent email."""
|
||||
|
||||
if intent_stage == models.INTENT_INCUBATE:
|
||||
if feature.feature_type == models.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Intent to Deprecate and Remove'
|
||||
elif intent_stage == models.INTENT_IMPLEMENT:
|
||||
return 'Intent to Prototype'
|
||||
elif intent_stage == models.INTENT_EXPERIMENT:
|
||||
return 'Ready for Trial'
|
||||
elif intent_stage == models.INTENT_EXTEND_TRIAL:
|
||||
if feature.feature_type == models.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Request for Deprecation Trial'
|
||||
else:
|
||||
return 'Intent to Experiment'
|
||||
elif intent_stage == models.INTENT_SHIP:
|
||||
return 'Intent to Ship'
|
||||
|
||||
return 'Intent stage "%s"' % models.INTENT_STAGES[intent_stage]
|
||||
|
||||
|
||||
class BlinkComponentHandler(basehandlers.FlaskHandler):
|
||||
"""Updates the list of Blink components in the db."""
|
||||
def get_template_data(self):
|
||||
|
@ -351,7 +287,4 @@ app = basehandlers.FlaskApplication([
|
|||
('/cron/metrics', YesterdayHandler),
|
||||
('/cron/histograms', HistogramsHandler),
|
||||
('/cron/update_blink_components', BlinkComponentHandler),
|
||||
('/admin/features/launch/<int:feature_id>', IntentEmailPreviewHandler),
|
||||
('/admin/features/launch/<int:feature_id>/<int:stage_id>',
|
||||
IntentEmailPreviewHandler),
|
||||
], debug=settings.DEBUG)
|
|
@ -0,0 +1,128 @@
|
|||
# Copyright 2020 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License")
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import datetime
|
||||
import unittest
|
||||
import testing_config # Must be imported before the module under test.
|
||||
import urllib
|
||||
|
||||
import mock
|
||||
import flask
|
||||
import werkzeug
|
||||
|
||||
from internals import fetchmetrics
|
||||
import models
|
||||
|
||||
|
||||
class FetchMetricsTest(unittest.TestCase):
|
||||
|
||||
@mock.patch('settings.PROD', True)
|
||||
@mock.patch('requests.request')
|
||||
def test__prod(self, mock_fetch):
|
||||
"""In prod, we actually request metrics from uma-export."""
|
||||
mock_fetch.return_value = 'mock response'
|
||||
actual = fetchmetrics._FetchMetrics('a url')
|
||||
|
||||
self.assertEqual('mock response', actual)
|
||||
mock_fetch.assert_called_once_with('GET',
|
||||
'a url', timeout=120, allow_redirects=False)
|
||||
|
||||
|
||||
@mock.patch('settings.STAGING', True)
|
||||
@mock.patch('requests.request')
|
||||
def test__staging(self, mock_fetch):
|
||||
"""In prod, we actually request metrics from uma-export."""
|
||||
mock_fetch.return_value = 'mock response'
|
||||
actual = fetchmetrics._FetchMetrics('a url')
|
||||
|
||||
self.assertEqual('mock response', actual)
|
||||
mock_fetch.assert_called_once_with('GET',
|
||||
'a url', timeout=120, allow_redirects=False)
|
||||
|
||||
@mock.patch('requests.request')
|
||||
def test__dev(self, mock_fetch):
|
||||
"""In Dev, we cannot access uma-export."""
|
||||
actual = fetchmetrics._FetchMetrics('a url')
|
||||
|
||||
self.assertIsNone(actual)
|
||||
mock_fetch.assert_not_called()
|
||||
|
||||
|
||||
class UmaQueryTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.uma_query = fetchmetrics.UmaQuery(
|
||||
query_name='usecounter.features',
|
||||
model_class=models.FeatureObserver,
|
||||
property_map_class=models.FeatureObserverHistogram)
|
||||
|
||||
def testHasCapstone__not_found(self):
|
||||
"""If there is no capstone entry, we get False."""
|
||||
query_date = datetime.date(2021, 1, 20)
|
||||
actual = self.uma_query._HasCapstone(query_date)
|
||||
self.assertFalse(actual)
|
||||
|
||||
def testHasCapstone__found(self):
|
||||
"""If we set a capstone entry, we can find it."""
|
||||
query_date = datetime.date(2021, 1, 20)
|
||||
capstone = self.uma_query._SetCapstone(query_date)
|
||||
|
||||
try:
|
||||
actual = self.uma_query._HasCapstone(query_date)
|
||||
finally:
|
||||
capstone.delete()
|
||||
|
||||
self.assertTrue(actual)
|
||||
|
||||
|
||||
class YesterdayHandlerTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.request_path = '/cron/metrics'
|
||||
self.handler = fetchmetrics.YesterdayHandler()
|
||||
|
||||
@mock.patch('internals.fetchmetrics.UmaQuery.FetchAndSaveData')
|
||||
def test_get__normal(self, mock_FetchAndSaveData):
|
||||
"""When requested with no date, we check the previous 5 days."""
|
||||
mock_FetchAndSaveData.return_value = 200
|
||||
today = datetime.date(2021, 1, 20)
|
||||
|
||||
with fetchmetrics.app.test_request_context(self.request_path):
|
||||
actual_response = self.handler.get_template_data(today=today)
|
||||
|
||||
self.assertEqual('Success', actual_response)
|
||||
expected_calls = [
|
||||
mock.call(datetime.date(2021, 1, day))
|
||||
for day in [19, 18, 17, 16, 15]
|
||||
for query in fetchmetrics.UMA_QUERIES]
|
||||
mock_FetchAndSaveData.assert_has_calls(expected_calls)
|
||||
|
||||
@mock.patch('internals.fetchmetrics.UmaQuery.FetchAndSaveData')
|
||||
def test_get__debugging(self, mock_FetchAndSaveData):
|
||||
"""We can request that the app get metrics for one specific day."""
|
||||
mock_FetchAndSaveData.return_value = 200
|
||||
today = datetime.date(2021, 1, 20)
|
||||
|
||||
with fetchmetrics.app.test_request_context(
|
||||
self.request_path, query_string={'date': '20210120'}):
|
||||
actual_response = self.handler.get_template_data(today=today)
|
||||
|
||||
self.assertEqual('Success', actual_response)
|
||||
expected_calls = [
|
||||
mock.call(datetime.date(2021, 1, 20))
|
||||
for query in fetchmetrics.UMA_QUERIES]
|
||||
mock_FetchAndSaveData.assert_has_calls(expected_calls)
|
|
@ -29,7 +29,7 @@ from google.appengine.api import mail
|
|||
from google.appengine.api import users
|
||||
|
||||
import models
|
||||
import notifier
|
||||
from internals import notifier
|
||||
import settings
|
||||
|
||||
|
||||
|
@ -177,7 +177,7 @@ class EmailFormattingTest(unittest.TestCase):
|
|||
actual = notifier.apply_subscription_rules(self.feature_1, changes)
|
||||
self.assertEqual({}, actual)
|
||||
|
||||
@mock.patch('notifier.format_email_body')
|
||||
@mock.patch('internals.notifier.format_email_body')
|
||||
def test_make_email_tasks__new(self, mock_f_e_b):
|
||||
"""We send email to component owners and subscribers for new features."""
|
||||
mock_f_e_b.return_value = 'mock body html'
|
||||
|
@ -194,7 +194,7 @@ class EmailFormattingTest(unittest.TestCase):
|
|||
mock_f_e_b.assert_called_once_with(
|
||||
False, self.feature_1, [])
|
||||
|
||||
@mock.patch('notifier.format_email_body')
|
||||
@mock.patch('internals.notifier.format_email_body')
|
||||
def test_make_email_tasks__update(self, mock_f_e_b):
|
||||
"""We send email to component owners and subscribers for edits."""
|
||||
mock_f_e_b.return_value = 'mock body html'
|
||||
|
@ -211,7 +211,7 @@ class EmailFormattingTest(unittest.TestCase):
|
|||
mock_f_e_b.assert_called_once_with(
|
||||
True, self.feature_1, self.changes)
|
||||
|
||||
@mock.patch('notifier.format_email_body')
|
||||
@mock.patch('internals.notifier.format_email_body')
|
||||
def test_make_email_tasks__starrer(self, mock_f_e_b):
|
||||
"""We send email to users who starred the feature."""
|
||||
mock_f_e_b.return_value = 'mock body html'
|
||||
|
@ -232,7 +232,7 @@ class EmailFormattingTest(unittest.TestCase):
|
|||
True, self.feature_1, self.changes)
|
||||
|
||||
|
||||
@mock.patch('notifier.format_email_body')
|
||||
@mock.patch('internals.notifier.format_email_body')
|
||||
def test_make_email_tasks__starrer_unsubscribed(self, mock_f_e_b):
|
||||
"""We don't email users who starred the feature but opted out."""
|
||||
mock_f_e_b.return_value = 'mock body html'
|
||||
|
@ -421,7 +421,7 @@ class BouncedEmailHandlerTest(unittest.TestCase):
|
|||
settings.APP_ID)
|
||||
self.expected_to = settings.BOUNCE_ESCALATION_ADDR
|
||||
|
||||
@mock.patch('notifier.BouncedEmailHandler.receive')
|
||||
@mock.patch('internals.notifier.BouncedEmailHandler.receive')
|
||||
def test_process_post_data(self, mock_receive):
|
||||
with notifier.app.test_request_context('/_ah/bounce'):
|
||||
actual_json = self.handler.process_post_data()
|
|
@ -23,7 +23,7 @@ import mock
|
|||
from google.appengine.ext import db
|
||||
|
||||
import models
|
||||
import processes
|
||||
from internals import processes
|
||||
|
||||
|
||||
class HelperFunctionsTest(unittest.TestCase):
|
|
@ -32,7 +32,7 @@ from google.appengine.api import users
|
|||
|
||||
from framework import cloud_tasks_helpers
|
||||
import settings
|
||||
import util
|
||||
from internals import fetchchannels
|
||||
|
||||
import hack_components
|
||||
import hack_wf_components
|
||||
|
@ -503,7 +503,7 @@ class Feature(DictModel):
|
|||
@classmethod
|
||||
def _annotate_first_of_milestones(self, feature_list, version=None):
|
||||
try:
|
||||
omaha_data = util.get_omaha_data()
|
||||
omaha_data = fetchchannels.get_omaha_data()
|
||||
|
||||
win_versions = omaha_data[0]['versions']
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
"deps": "pip install -t lib -r requirements.txt --upgrade",
|
||||
"travis-deps": "pip install -t lib -r requirements.travis.txt --upgrade",
|
||||
"dev-deps": "pip install -r requirements.dev.txt --upgrade",
|
||||
"test": "python -m unittest discover -p *_test.py -b",
|
||||
"coverage": "python -m coverage erase && python -m coverage run -m unittest discover -p *_test.py -s tests -b && python -m coverage html",
|
||||
"test": "python -m unittest discover -p '*_test.py' -b",
|
||||
"coverage": "python -m coverage erase && python -m coverage run -m unittest discover -p '*_test.py' -b && python -m coverage html",
|
||||
"lint": "gulp lint-fix && lit-analyzer \"static/elements/chromedash-!(featurelist)*.js\"",
|
||||
"build": "gulp",
|
||||
"watch": "gulp watch",
|
||||
|
|
|
@ -28,7 +28,6 @@ from framework import basehandlers
|
|||
from framework import permissions
|
||||
import models
|
||||
import settings
|
||||
import util
|
||||
from schedule import construct_chrome_channels_details
|
||||
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import settings
|
|||
from framework import basehandlers
|
||||
from pages import guideforms
|
||||
import models
|
||||
import processes
|
||||
from internals import processes
|
||||
|
||||
|
||||
class FeatureDetailHandler(basehandlers.FlaskHandler):
|
||||
|
|
|
@ -34,7 +34,7 @@ from framework import basehandlers
|
|||
from framework import utils
|
||||
from pages import guideforms
|
||||
import models
|
||||
import processes
|
||||
from internals import processes
|
||||
import settings
|
||||
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ from django import forms
|
|||
from google.appengine.api import users
|
||||
|
||||
import models
|
||||
import processes
|
||||
from internals import processes
|
||||
|
||||
SHIPPED_HELP_TXT = (
|
||||
'First milestone to ship with this status. Applies to: Enabled by '
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2021 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License")
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from google.appengine.api import users
|
||||
|
||||
import models
|
||||
import settings
|
||||
from framework import basehandlers
|
||||
from internals import processes
|
||||
|
||||
INTENT_PARAM = 'intent'
|
||||
LAUNCH_PARAM = 'launch'
|
||||
VIEW_FEATURE_URL = '/feature'
|
||||
|
||||
|
||||
class IntentEmailPreviewHandler(basehandlers.FlaskHandler):
|
||||
"""Show a preview of an intent email, as appropriate to the feature stage."""
|
||||
|
||||
TEMPLATE_PATH = 'admin/features/launch.html'
|
||||
|
||||
def get_template_data(self, feature_id=None, stage_id=None):
|
||||
user = users.get_current_user()
|
||||
if user is None:
|
||||
return self.redirect(users.create_login_url(self.request.path))
|
||||
|
||||
if not feature_id:
|
||||
self.abort(404)
|
||||
f = models.Feature.get_by_id(feature_id)
|
||||
if f is None:
|
||||
self.abort(404)
|
||||
|
||||
intent_stage = stage_id if stage_id is not None else f.intent_stage
|
||||
|
||||
if not self.user_can_edit(user):
|
||||
self.abort(403)
|
||||
|
||||
page_data = self.get_page_data(feature_id, f, intent_stage)
|
||||
return page_data
|
||||
|
||||
def get_page_data(self, feature_id, f, intent_stage):
|
||||
"""Return a dictionary of data used to render the page."""
|
||||
page_data = {
|
||||
'subject_prefix': self.compute_subject_prefix(f, intent_stage),
|
||||
'feature': f.format_for_template(),
|
||||
'sections_to_show': processes.INTENT_EMAIL_SECTIONS.get(
|
||||
intent_stage, []),
|
||||
'intent_stage': intent_stage,
|
||||
'default_url': '%s://%s%s/%s' % (
|
||||
self.request.scheme, self.request.host,
|
||||
VIEW_FEATURE_URL, feature_id),
|
||||
}
|
||||
|
||||
if LAUNCH_PARAM in self.request.args:
|
||||
page_data[LAUNCH_PARAM] = True
|
||||
if INTENT_PARAM in self.request.args:
|
||||
page_data[INTENT_PARAM] = True
|
||||
|
||||
return page_data
|
||||
|
||||
def compute_subject_prefix(self, feature, intent_stage):
|
||||
"""Return part of the subject line for an intent email."""
|
||||
|
||||
if intent_stage == models.INTENT_INCUBATE:
|
||||
if feature.feature_type == models.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Intent to Deprecate and Remove'
|
||||
elif intent_stage == models.INTENT_IMPLEMENT:
|
||||
return 'Intent to Prototype'
|
||||
elif intent_stage == models.INTENT_EXPERIMENT:
|
||||
return 'Ready for Trial'
|
||||
elif intent_stage == models.INTENT_EXTEND_TRIAL:
|
||||
if feature.feature_type == models.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Request for Deprecation Trial'
|
||||
else:
|
||||
return 'Intent to Experiment'
|
||||
elif intent_stage == models.INTENT_SHIP:
|
||||
return 'Intent to Ship'
|
||||
|
||||
return 'Intent stage "%s"' % models.INTENT_STAGES[intent_stage]
|
||||
|
||||
app = basehandlers.FlaskApplication([
|
||||
('/admin/features/launch/<int:feature_id>', IntentEmailPreviewHandler),
|
||||
('/admin/features/launch/<int:feature_id>/<int:stage_id>',
|
||||
IntentEmailPreviewHandler),
|
||||
], debug=settings.DEBUG)
|
|
@ -15,117 +15,16 @@
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import datetime
|
||||
import unittest
|
||||
import testing_config # Must be imported before the module under test.
|
||||
import urllib
|
||||
|
||||
import mock
|
||||
import flask
|
||||
import werkzeug
|
||||
|
||||
import admin
|
||||
from pages import intentpreview
|
||||
import models
|
||||
import processes
|
||||
|
||||
class FetchMetricsTest(unittest.TestCase):
|
||||
|
||||
@mock.patch('settings.PROD', True)
|
||||
@mock.patch('requests.request')
|
||||
def test__prod(self, mock_fetch):
|
||||
"""In prod, we actually request metrics from uma-export."""
|
||||
mock_fetch.return_value = 'mock response'
|
||||
actual = admin._FetchMetrics('a url')
|
||||
|
||||
self.assertEqual('mock response', actual)
|
||||
mock_fetch.assert_called_once_with('GET',
|
||||
'a url', timeout=120, allow_redirects=False)
|
||||
|
||||
|
||||
@mock.patch('settings.STAGING', True)
|
||||
@mock.patch('requests.request')
|
||||
def test__staging(self, mock_fetch):
|
||||
"""In prod, we actually request metrics from uma-export."""
|
||||
mock_fetch.return_value = 'mock response'
|
||||
actual = admin._FetchMetrics('a url')
|
||||
|
||||
self.assertEqual('mock response', actual)
|
||||
mock_fetch.assert_called_once_with('GET',
|
||||
'a url', timeout=120, allow_redirects=False)
|
||||
|
||||
@mock.patch('requests.request')
|
||||
def test__dev(self, mock_fetch):
|
||||
"""In Dev, we cannot access uma-export."""
|
||||
actual = admin._FetchMetrics('a url')
|
||||
|
||||
self.assertIsNone(actual)
|
||||
mock_fetch.assert_not_called()
|
||||
|
||||
|
||||
class UmaQueryTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.uma_query = admin.UmaQuery(
|
||||
query_name='usecounter.features',
|
||||
model_class=models.FeatureObserver,
|
||||
property_map_class=models.FeatureObserverHistogram)
|
||||
|
||||
def testHasCapstone__not_found(self):
|
||||
"""If there is no capstone entry, we get False."""
|
||||
query_date = datetime.date(2021, 1, 20)
|
||||
actual = self.uma_query._HasCapstone(query_date)
|
||||
self.assertFalse(actual)
|
||||
|
||||
def testHasCapstone__found(self):
|
||||
"""If we set a capstone entry, we can find it."""
|
||||
query_date = datetime.date(2021, 1, 20)
|
||||
capstone = self.uma_query._SetCapstone(query_date)
|
||||
|
||||
try:
|
||||
actual = self.uma_query._HasCapstone(query_date)
|
||||
finally:
|
||||
capstone.delete()
|
||||
|
||||
self.assertTrue(actual)
|
||||
|
||||
|
||||
class YesterdayHandlerTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.request_path = '/cron/metrics'
|
||||
self.handler = admin.YesterdayHandler()
|
||||
|
||||
@mock.patch('admin.UmaQuery.FetchAndSaveData')
|
||||
def test_get__normal(self, mock_FetchAndSaveData):
|
||||
"""When requested with no date, we check the previous 5 days."""
|
||||
mock_FetchAndSaveData.return_value = 200
|
||||
today = datetime.date(2021, 1, 20)
|
||||
|
||||
with admin.app.test_request_context(self.request_path):
|
||||
actual_response = self.handler.get_template_data(today=today)
|
||||
|
||||
self.assertEqual('Success', actual_response)
|
||||
expected_calls = [
|
||||
mock.call(datetime.date(2021, 1, day))
|
||||
for day in [19, 18, 17, 16, 15]
|
||||
for query in admin.UMA_QUERIES]
|
||||
mock_FetchAndSaveData.assert_has_calls(expected_calls)
|
||||
|
||||
@mock.patch('admin.UmaQuery.FetchAndSaveData')
|
||||
def test_get__debugging(self, mock_FetchAndSaveData):
|
||||
"""We can request that the app get metrics for one specific day."""
|
||||
mock_FetchAndSaveData.return_value = 200
|
||||
today = datetime.date(2021, 1, 20)
|
||||
|
||||
with admin.app.test_request_context(
|
||||
self.request_path, query_string={'date': '20210120'}):
|
||||
actual_response = self.handler.get_template_data(today=today)
|
||||
|
||||
self.assertEqual('Success', actual_response)
|
||||
expected_calls = [
|
||||
mock.call(datetime.date(2021, 1, 20))
|
||||
for query in admin.UMA_QUERIES]
|
||||
mock_FetchAndSaveData.assert_has_calls(expected_calls)
|
||||
|
||||
|
||||
class IntentEmailPreviewHandlerTest(unittest.TestCase):
|
||||
|
@ -139,7 +38,7 @@ class IntentEmailPreviewHandlerTest(unittest.TestCase):
|
|||
|
||||
self.request_path = '/admin/features/launch/%d/%d?intent' % (
|
||||
models.INTENT_SHIP, self.feature_1.key().id())
|
||||
self.handler = admin.IntentEmailPreviewHandler()
|
||||
self.handler = intentpreview.IntentEmailPreviewHandler()
|
||||
|
||||
def tearDown(self):
|
||||
self.feature_1.delete()
|
||||
|
@ -148,7 +47,7 @@ class IntentEmailPreviewHandlerTest(unittest.TestCase):
|
|||
"""Anon cannot view this preview features, gets redirected to login."""
|
||||
testing_config.sign_out()
|
||||
feature_id = self.feature_1.key().id()
|
||||
with admin.app.test_request_context(self.request_path):
|
||||
with intentpreview.app.test_request_context(self.request_path):
|
||||
actual_response = self.handler.get_template_data(feature_id=feature_id)
|
||||
self.assertEqual('302 FOUND', actual_response.status)
|
||||
|
||||
|
@ -156,7 +55,7 @@ class IntentEmailPreviewHandlerTest(unittest.TestCase):
|
|||
"""Trying to view a feature that does not exist gives a 404."""
|
||||
testing_config.sign_in('user1@google.com', 123567890)
|
||||
bad_feature_id = self.feature_1.key().id() + 1
|
||||
with admin.app.test_request_context(self.request_path):
|
||||
with intentpreview.app.test_request_context(self.request_path):
|
||||
with self.assertRaises(werkzeug.exceptions.NotFound):
|
||||
self.handler.get_template_data(feature_id=bad_feature_id)
|
||||
|
||||
|
@ -166,7 +65,7 @@ class IntentEmailPreviewHandlerTest(unittest.TestCase):
|
|||
'/admin/features/launch/%d?intent' % self.feature_1.key().id())
|
||||
testing_config.sign_in('user1@google.com', 123567890)
|
||||
feature_id = self.feature_1.key().id()
|
||||
with admin.app.test_request_context(self.request_path):
|
||||
with intentpreview.app.test_request_context(self.request_path):
|
||||
actual_data = self.handler.get_template_data(feature_id=feature_id)
|
||||
self.assertIn('feature', actual_data)
|
||||
self.assertEqual('feature one', actual_data['feature']['name'])
|
||||
|
@ -175,7 +74,7 @@ class IntentEmailPreviewHandlerTest(unittest.TestCase):
|
|||
"""Allowed user can preview intent email for a feature."""
|
||||
testing_config.sign_in('user1@google.com', 123567890)
|
||||
feature_id = self.feature_1.key().id()
|
||||
with admin.app.test_request_context(self.request_path):
|
||||
with intentpreview.app.test_request_context(self.request_path):
|
||||
actual_data = self.handler.get_template_data(feature_id=feature_id)
|
||||
self.assertIn('feature', actual_data)
|
||||
self.assertEqual('feature one', actual_data['feature']['name'])
|
||||
|
@ -183,7 +82,7 @@ class IntentEmailPreviewHandlerTest(unittest.TestCase):
|
|||
def test_get_page_data(self):
|
||||
"""page_data has correct values."""
|
||||
feature_id = self.feature_1.key().id()
|
||||
with admin.app.test_request_context(self.request_path):
|
||||
with intentpreview.app.test_request_context(self.request_path):
|
||||
page_data = self.handler.get_page_data(
|
||||
feature_id, self.feature_1, models.INTENT_IMPLEMENT)
|
||||
self.assertEqual(
|
|
@ -24,10 +24,9 @@ from framework import basehandlers
|
|||
from framework import utils
|
||||
from pages import guideforms
|
||||
import models
|
||||
import processes
|
||||
from framework import ramcache
|
||||
from framework import utils
|
||||
import util
|
||||
from internals import fetchchannels
|
||||
|
||||
from google.appengine.api import users
|
||||
|
||||
|
@ -74,7 +73,7 @@ class OmahaDataHandler(basehandlers.FlaskHandler):
|
|||
JSONIFY = True
|
||||
|
||||
def get_template_data(self):
|
||||
omaha_data = util.get_omaha_data()
|
||||
omaha_data = fetchchannels.get_omaha_data()
|
||||
return omaha_data
|
||||
|
||||
|
||||
|
@ -99,8 +98,6 @@ routes = [
|
|||
('/metrics/feature/timeline/popularity', FeaturePopularityHandler),
|
||||
('/metrics/feature/timeline/popularity/<int:bucket_id>', FeaturePopularityHandler),
|
||||
|
||||
# TODO(jrobbins): util.py has only one thing in it, so maybe move
|
||||
# it and this handler to a new omaha.py file.
|
||||
('/omaha_data', OmahaDataHandler),
|
||||
]
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ from google.appengine.api import users
|
|||
from framework import basehandlers
|
||||
import models
|
||||
import settings
|
||||
import util
|
||||
from internals import fetchchannels
|
||||
|
||||
SCHEDULE_CACHE_TIME = 60 * 60 # 1 hour
|
||||
|
||||
|
@ -67,7 +67,7 @@ def fetch_chrome_release_info(version):
|
|||
return data
|
||||
|
||||
def construct_chrome_channels_details():
|
||||
omaha_data = util.get_omaha_data()
|
||||
omaha_data = fetchchannels.get_omaha_data()
|
||||
channels = {}
|
||||
win_versions = omaha_data[0]['versions']
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ from pages import schedule
|
|||
class ScheduleFunctionsTest(unittest.TestCase):
|
||||
|
||||
@mock.patch('pages.schedule.fetch_chrome_release_info')
|
||||
@mock.patch('util.get_omaha_data')
|
||||
@mock.patch('internals.fetchchannels.get_omaha_data')
|
||||
def test_construct_chrome_channels_details(
|
||||
self, mock_get_omaha_data, mock_fetch_chrome_release_info):
|
||||
win_data = {
|
||||
|
|
Загрузка…
Ссылка в новой задаче