Skip to content

Commit ee98f64

Browse files
committed
Merge branch 'dev' of github.com:biocore/qiita into dev
2 parents 13eafdf + 83476d5 commit ee98f64

22 files changed

+607
-41
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,6 @@ qiita_pet/*.conf
7272

7373
# jupyter notebooks input data
7474
notebooks/*/*.tsv.gz
75+
76+
# jupyter notebooks input data
77+
notebooks/resource-allocation/data

qiita_core/configuration_manager.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ class ConfigurationManager(object):
109109
The portal subdirectory used in the URL
110110
portal_fp : str
111111
The filepath to the portal styling config file
112+
stats_map_center_latitude : float
113+
The center latitude of the world map, shown on the Stats map.
114+
Defaults to 40.01027 (Boulder, CO, USA)
115+
stats_map_center_longitude : float
116+
The center longitude of the world map, shown on the Stats map.
117+
Defaults to -105.24827 (Boulder, CO, USA)
112118
qiita_env : str
113119
The script used to start the qiita environment
114120
private_launcher : str
@@ -117,6 +123,10 @@ class ConfigurationManager(object):
117123
The script used to start the plugins
118124
plugin_dir : str
119125
The path to the directory containing the plugin configuration files
126+
help_email : str
127+
The email address a user should write to when asking for help
128+
sysadmin_email : str
129+
The email address, Qiita sends internal notifications to a sys admin
120130
121131
Raises
122132
------
@@ -234,6 +244,32 @@ def _get_main(self, config):
234244
self.key_file = join(install_dir, 'qiita_core', 'support_files',
235245
'ci_server.key')
236246

247+
self.help_email = config.get('main', 'HELP_EMAIL')
248+
if not self.help_email:
249+
raise ValueError(
250+
"You did not specify the HELP_EMAIL address in the main "
251+
"section of Qiita's config file. This address is essential "
252+
"for users to ask for help as it is displayed at various "
253+
"location throughout Qiita's web pages.")
254+
if (self.help_email == '[email protected]') and \
255+
(self.test_environment is False):
256+
warnings.warn(
257+
"Using the github fake email for HELP_EMAIL, "
258+
"are you sure this is OK?")
259+
260+
self.sysadmin_email = config.get('main', 'SYSADMIN_EMAIL')
261+
if not self.sysadmin_email:
262+
raise ValueError(
263+
"You did not specify the SYSADMIN_EMAIL address in the main "
264+
"section of Qiita's config file. Serious issues will "
265+
"automatically be reported to a sys admin, an according "
266+
"address is therefore required!")
267+
if (self.sysadmin_email == '[email protected]') and \
268+
(self.test_environment is False):
269+
warnings.warn(
270+
"Using the github fake email for SYSADMIN_EMAIL, "
271+
"are you sure this is OK?")
272+
237273
def _get_job_scheduler(self, config):
238274
"""Get the configuration of the job_scheduler section"""
239275
self.job_scheduler_owner = config.get(
@@ -317,5 +353,40 @@ def _get_portal(self, config):
317353
else:
318354
self.portal_dir = ""
319355

356+
msg = ("The value %s for %s you set in Qiita's configuration file "
357+
"(section 'portal') for the Stats world map cannot be "
358+
"intepreted as a float! %s")
359+
lat_default = 40.01027 # Boulder CO, USA
360+
try:
361+
self.stats_map_center_latitude = config.get(
362+
'portal', 'STATS_MAP_CENTER_LATITUDE', fallback=lat_default)
363+
if self.stats_map_center_latitude == '':
364+
self.stats_map_center_latitude = lat_default
365+
self.stats_map_center_latitude = float(
366+
self.stats_map_center_latitude)
367+
except ValueError as e:
368+
raise ValueError(msg % (self.stats_map_center_latitude,
369+
'STATS_MAP_CENTER_LATITUDE', e))
370+
371+
lon_default = -105.24827 # Boulder CO, USA
372+
try:
373+
self.stats_map_center_longitude = config.get(
374+
'portal', 'STATS_MAP_CENTER_LONGITUDE', fallback=lon_default)
375+
if self.stats_map_center_longitude == '':
376+
self.stats_map_center_longitude = lon_default
377+
self.stats_map_center_longitude = float(
378+
self.stats_map_center_longitude)
379+
except ValueError as e:
380+
raise ValueError(msg % (self.stats_map_center_longitude,
381+
'STATS_MAP_CENTER_LONGITUDE', e))
382+
for (name, val) in [('latitude', self.stats_map_center_latitude),
383+
('longitude', self.stats_map_center_longitude)]:
384+
msg = ("The %s of %s you set in Qiita's configuration file "
385+
"(section 'portal') for the Stats world map cannot be %s!")
386+
if val < -180:
387+
raise ValueError(msg % (name, val, 'smaller than -180°'))
388+
if val > 180:
389+
raise ValueError(msg % (name, val, 'larger than 180°'))
390+
320391
def _iframe(self, config):
321392
self.iframe_qiimp = config.get('iframe', 'QIIMP')

qiita_core/support_files/config_test.cfg

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ COOKIE_SECRET = SECRET
6868
# The value used to secure JWTs for delegated permission artifact download.
6969
JWT_SECRET = SUPER_SECRET
7070

71+
# Address a user should write to when asking for help
72+
HELP_EMAIL = [email protected]
73+
74+
# The email address, Qiita sends internal notifications to a sys admin
75+
SYSADMIN_EMAIL = [email protected]
76+
7177
# ----------------------------- SMTP settings -----------------------------
7278
[smtp]
7379
# The hostname to connect to
@@ -177,6 +183,13 @@ PORTAL_DIR =
177183
# Full path to portal styling config file
178184
PORTAL_FP =
179185

186+
# The center latitude of the world map, shown on the Stats map.
187+
# Defaults to 40.01027 (Boulder, CO, USA)
188+
STATS_MAP_CENTER_LATITUDE =
189+
190+
# The center longitude of the world map, shown on the Stats map.
191+
# Defaults to -105.24827 (Boulder, CO, USA)
192+
STATS_MAP_CENTER_LONGITUDE =
180193

181194
# ----------------------------- iframes settings ---------------------------
182195
[iframe]

qiita_core/tests/test_configuration_manager.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def setUp(self):
2929

3030
self.conf = ConfigParser()
3131
with open(self.conf_fp, newline=None) as f:
32-
self.conf.readfp(f)
32+
self.conf.read_file(f)
3333

3434
def tearDown(self):
3535
if self.old_conf_fp is not None:
@@ -132,6 +132,8 @@ def test_get_main(self):
132132

133133
# Warning raised if No files will be allowed to be uploaded
134134
# Warning raised if no cookie_secret
135+
self.conf.set('main', 'HELP_EMAIL', 'ignore@me')
136+
self.conf.set('main', 'SYSADMIN_EMAIL', 'ignore@me')
135137
with warnings.catch_warnings(record=True) as warns:
136138
obs._get_main(self.conf)
137139

@@ -180,6 +182,35 @@ def test_get_main(self):
180182

181183
self.assertEqual(obs.qiita_env, "")
182184

185+
def test_help_email(self):
186+
obs = ConfigurationManager()
187+
188+
with warnings.catch_warnings(record=True) as warns:
189+
# warning get only issued when in non test environment
190+
self.conf.set('main', 'TEST_ENVIRONMENT', 'FALSE')
191+
192+
obs._get_main(self.conf)
193+
self.assertEqual(obs.help_email, '[email protected]')
194+
self.assertEqual(obs.sysadmin_email, '[email protected]')
195+
196+
obs_warns = [str(w.message) for w in warns]
197+
exp_warns = [
198+
'Using the github fake email for HELP_EMAIL, '
199+
'are you sure this is OK?',
200+
'Using the github fake email for SYSADMIN_EMAIL, '
201+
'are you sure this is OK?']
202+
self.assertCountEqual(obs_warns, exp_warns)
203+
204+
# test if it falls back to [email protected]
205+
self.conf.set('main', 'HELP_EMAIL', '')
206+
with self.assertRaises(ValueError):
207+
obs._get_main(self.conf)
208+
209+
# test if it falls back to [email protected]
210+
self.conf.set('main', 'SYSADMIN_EMAIL', '')
211+
with self.assertRaises(ValueError):
212+
obs._get_main(self.conf)
213+
183214
def test_get_job_scheduler(self):
184215
obs = ConfigurationManager()
185216

@@ -214,6 +245,50 @@ def test_get_portal(self):
214245
obs._get_portal(self.conf)
215246
self.assertEqual(obs.portal_dir, "/gold_portal")
216247

248+
def test_get_portal_latlong(self):
249+
obs = ConfigurationManager()
250+
251+
# if parameters are given, but not set, they should default to Boulder
252+
self.assertEqual(obs.stats_map_center_latitude, 40.01027)
253+
self.assertEqual(obs.stats_map_center_longitude, -105.24827)
254+
255+
# a string cannot be parsed as a float
256+
self.conf.set('portal', 'STATS_MAP_CENTER_LATITUDE', 'kurt')
257+
with self.assertRaises(ValueError):
258+
obs._get_portal(self.conf)
259+
260+
# check for illegal float values
261+
self.conf.set('portal', 'STATS_MAP_CENTER_LATITUDE', "-200")
262+
with self.assertRaises(ValueError):
263+
obs._get_portal(self.conf)
264+
self.conf.set('portal', 'STATS_MAP_CENTER_LATITUDE', "200")
265+
with self.assertRaises(ValueError):
266+
obs._get_portal(self.conf)
267+
268+
# check if value defaults if option is missing altogether
269+
self.conf.remove_option('portal', 'STATS_MAP_CENTER_LATITUDE')
270+
obs._get_portal(self.conf)
271+
self.assertEqual(obs.stats_map_center_latitude, 40.01027)
272+
273+
# same as above, but for longitude
274+
# a string cannot be parsed as a float
275+
self.conf.set('portal', 'STATS_MAP_CENTER_LONGITUDE', 'kurt')
276+
with self.assertRaises(ValueError):
277+
obs._get_portal(self.conf)
278+
279+
# check for illegal float values
280+
self.conf.set('portal', 'STATS_MAP_CENTER_LONGITUDE', "-200")
281+
with self.assertRaises(ValueError):
282+
obs._get_portal(self.conf)
283+
self.conf.set('portal', 'STATS_MAP_CENTER_LONGITUDE', "200")
284+
with self.assertRaises(ValueError):
285+
obs._get_portal(self.conf)
286+
287+
# check if value defaults if option is missing altogether
288+
self.conf.remove_option('portal', 'STATS_MAP_CENTER_LONGITUDE')
289+
obs._get_portal(self.conf)
290+
self.assertEqual(obs.stats_map_center_longitude, -105.24827)
291+
217292

218293
CONF = """
219294
# ------------------------------ Main settings --------------------------------
@@ -274,6 +349,12 @@ def test_get_portal(self):
274349
# The value used to secure JWTs for delegated permission artifact download.
275350
JWT_SECRET = SUPER_SECRET
276351
352+
# Address a user should write to when asking for help
353+
HELP_EMAIL = [email protected]
354+
355+
# The email address, Qiita sends internal notifications to a sys admin
356+
SYSADMIN_EMAIL = [email protected]
357+
277358
# ----------------------------- SMTP settings -----------------------------
278359
[smtp]
279360
# The hostname to connect to
@@ -380,6 +461,14 @@ def test_get_portal(self):
380461
# Full path to portal styling config file
381462
PORTAL_FP = /tmp/portal.cfg
382463
464+
# The center latitude of the world map, shown on the Stats map.
465+
# Defaults to 40.01027 (Boulder, CO, USA)
466+
STATS_MAP_CENTER_LATITUDE =
467+
468+
# The center longitude of the world map, shown on the Stats map.
469+
# Defaults to -105.24827 (Boulder, CO, USA)
470+
STATS_MAP_CENTER_LONGITUDE =
471+
383472
# ----------------------------- iframes settings ---------------------------
384473
[iframe]
385474
QIIMP = https://localhost:8898/

qiita_db/processing_job.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ def resource_allocation_info(self):
493493
samples, columns, input_size = self.shape
494494
parts = []
495495
error_msg = ('Obvious incorrect allocation. Please '
496-
'contact [email protected]')
496+
'contact %s' % qiita_config.help_email)
497497
for part in allocation.split('--'):
498498
param = ''
499499
if part.startswith('time '):
@@ -902,7 +902,7 @@ def _set_status(self, value, error_msg=None):
902902
if self.user.level in {'admin', 'wet-lab admin'}:
903903
if value == 'error':
904904
qdb.util.send_email(
905-
'[email protected]', msg['subject'],
905+
qiita_config.sysadmin_email, msg['subject'],
906906
msg['message'])
907907

908908
sql = """UPDATE qiita.processing_job

qiita_db/study.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,7 @@ def artifacts(self, dtype=None, artifact_type=None):
10921092

10931093
if artifact_type:
10941094
sql_args.append(artifact_type)
1095-
sql_where += "AND artifact_type = %s"
1095+
sql_where += " AND artifact_type = %s"
10961096

10971097
sql = """SELECT artifact_id
10981098
FROM qiita.artifact
7.17 MB
Binary file not shown.

qiita_db/test/test_util.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
from qiita_core.util import qiita_test_checker
2222
import qiita_db as qdb
2323

24+
from matplotlib.figure import Figure
25+
from matplotlib.axes import Axes
26+
import matplotlib.pyplot as plt
27+
2428

2529
@qiita_test_checker()
2630
class DBUtilTestsBase(TestCase):
@@ -1303,6 +1307,64 @@ def test_quick_mounts_purge(self):
13031307
qdb.util.quick_mounts_purge()
13041308

13051309

1310+
class ResourceAllocationPlotTests(TestCase):
1311+
def setUp(self):
1312+
1313+
self.PATH_TO_DATA = ('./qiita_db/test/test_data/'
1314+
'jobs_2024-02-21.tsv.gz')
1315+
self.CNAME = "Validate"
1316+
self.SNAME = "Diversity types - alpha_vector"
1317+
self.col_name = 'samples * columns'
1318+
self.df = pd.read_csv(self.PATH_TO_DATA, sep='\t',
1319+
dtype={'extra_info': str})
1320+
1321+
def test_plot_return(self):
1322+
# check the plot returns correct objects
1323+
fig1, axs1 = qdb.util.resource_allocation_plot(
1324+
self.PATH_TO_DATA, self.CNAME, self.SNAME, self.col_name)
1325+
self.assertIsInstance(
1326+
fig1, Figure,
1327+
"Returned object fig1 is not a Matplotlib Figure")
1328+
for ax in axs1:
1329+
self.assertIsInstance(
1330+
ax, Axes,
1331+
"Returned object axs1 is not a single Matplotlib Axes object")
1332+
1333+
def test_minimize_const(self):
1334+
self.df = self.df[
1335+
(self.df.cName == self.CNAME) & (self.df.sName == self.SNAME)]
1336+
self.df.dropna(subset=['samples', 'columns'], inplace=True)
1337+
self.df[self.col_name] = self.df.samples * self.df['columns']
1338+
fig, axs = plt.subplots(ncols=2, figsize=(10, 4), sharey=False)
1339+
1340+
bm, options = qdb.util._resource_allocation_plot_helper(
1341+
self.df, axs[0], self.CNAME, self.SNAME, 'MaxRSSRaw',
1342+
qdb.util.MODELS_MEM, self.col_name)
1343+
# check that the algorithm chooses correct model for MaxRSSRaw and
1344+
# has 0 failures
1345+
k, a, b = options.x
1346+
failures_df = qdb.util._resource_allocation_failures(
1347+
self.df, k, a, b, bm, self.col_name, 'MaxRSSRaw')
1348+
failures = failures_df.shape[0]
1349+
self.assertEqual(bm, qdb.util.mem_model4, msg="""Best memory model
1350+
doesn't match""")
1351+
self.assertEqual(failures, 0, "Number of failures must be 0")
1352+
1353+
# check that the algorithm chooses correct model for ElapsedRaw and
1354+
# has 1 failure
1355+
bm, options = qdb.util._resource_allocation_plot_helper(
1356+
self.df, axs[1], self.CNAME, self.SNAME, 'ElapsedRaw',
1357+
qdb.util.MODELS_TIME, self.col_name)
1358+
k, a, b = options.x
1359+
failures_df = qdb.util._resource_allocation_failures(
1360+
self.df, k, a, b, bm, self.col_name, 'ElapsedRaw')
1361+
failures = failures_df.shape[0]
1362+
1363+
self.assertEqual(bm, qdb.util.time_model1, msg="""Best time model
1364+
doesn't match""")
1365+
self.assertEqual(failures, 1, "Number of failures must be 1")
1366+
1367+
13061368
STUDY_INFO = {
13071369
'study_id': 1,
13081370
'owner': 'Dude',

0 commit comments

Comments
 (0)