Merge lp:~rharding/launchpad/info_type_events into lp:launchpad

Proposed by Richard Harding
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 16037
Proposed branch: lp:~rharding/launchpad/info_type_events
Merge into: lp:launchpad
Diff against target: 659 lines (+357/-85)
9 files modified
lib/lp/app/javascript/banners/banner.js (+24/-20)
lib/lp/app/javascript/banners/beta-notification.js (+53/-33)
lib/lp/app/javascript/banners/privacy.js (+104/-20)
lib/lp/app/javascript/banners/tests/test_privacy.html (+2/-3)
lib/lp/app/javascript/banners/tests/test_privacy.js (+32/-3)
lib/lp/app/javascript/information_type.js (+63/-0)
lib/lp/app/javascript/tests/test_information_type.js (+75/-0)
lib/lp/blueprints/browser/specification.py (+2/-4)
lib/lp/bugs/javascript/filebug.js (+2/-2)
To merge this branch: bzr merge lp:~rharding/launchpad/info_type_events
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+126317@code.launchpad.net

Commit message

Add events to information_type and privacy banner to drive usage.

Description of the change

= Summary =

This is the first of a series of branches to update the PrivacyBanner to be
event driven. The end goal is that the information type changes at the widget
level will fire global events that will update the banner appropriately.

== Pre Implementation ==

Talked with JC about how things currently worked.

== Implementation Notes ==

This branch includes some general cleanup and linting. Sorting ATTRS into
alphabetical order, moving functions up above the class definitions.

It also includes one drive by import warning around #636 of the diff.

This adds published events to the information_type module. Currently, these
are not fired in real code, but tests are added that you can fire an event
that the information type has changed, and it'll determine if the new value is
public or private and fire a secondary event with that info.

It also adds logic and events to the PrivacyBanner. It watches for changes to
the information type so that it can know if it should show/hide. PrivacyBanner
is also used for security notification so it needs manual events so that when
something is a security issue, it can be told to show, but with a custom
message and nothing to do with information type itself.

== Tests ==

test_information_type.html
test_privacy.html

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/javascript/banners/banner.js'
2--- lib/lp/app/javascript/banners/banner.js 2012-05-25 14:38:42 +0000
3+++ lib/lp/app/javascript/banners/banner.js 2012-09-26 13:33:25 +0000
4@@ -1,4 +1,5 @@
5-/* Copyright 2012 Canonical Ltd. This software is licensed under the
6+/*
7+ * Copyright 2012 Canonical Ltd. This software is licensed under the
8 * GNU Affero General Public License version 3 (see the file LICENSE).
9 *
10 * Notification banner widget
11@@ -29,10 +30,11 @@
12
13 _showBanner: function () {
14 var body = Y.one('body');
15+ var global_notification = Y.one('.global-notification');
16+ var anim_times = this._getAnimTimes();
17+
18 body.addClass('global-notification-visible');
19- var global_notification = Y.one('.global-notification');
20 global_notification.removeClass('hidden');
21- var anim_times = this._getAnimTimes();
22
23 var fade_in = new Y.Anim({
24 node: global_notification,
25@@ -59,10 +61,10 @@
26 _hideBanner: function () {
27 var body = Y.one('body');
28 var global_notification = Y.one('.global-notification');
29+ var anim_times = this._getAnimTimes();
30+
31 global_notification.addClass('transparent');
32
33- var anim_times = this._getAnimTimes();
34-
35 var fade_out = new Y.Anim({
36 node: global_notification,
37 to: {opacity: 0},
38@@ -92,38 +94,37 @@
39 login_space.run();
40 },
41
42+ bindUI: function() {
43+ this.after('visibleChange', function() {
44+ if (this.get('visible')) {
45+ this._showBanner();
46+ } else {
47+ this._hideBanner();
48+ }
49+ });
50+ },
51+
52 renderUI: function () {
53 var banner_data = {
54 badge: this.get('banner_icon'),
55 text: this.get('notification_text')
56 };
57- var banner_html = Y.lp.mustache.to_html(
58+ var banner_html = Y.lp.mustache.to_html(
59 this.get('banner_template'),
60 banner_data);
61- this.get('contentBox').append(banner_html);
62- },
63-
64- bindUI: function() {
65- this.after('visibleChange', function() {
66- if (this.get('visible')) {
67- this._showBanner();
68- } else {
69- this._hideBanner();
70- }
71- });
72+ this.get('contentBox').append(banner_html);
73 },
74
75 updateText: function (new_text) {
76+ var text_node = this.get('contentBox').one('.banner-text');
77 if (!Y.Lang.isValue(new_text)) {
78 new_text = this.get('notification_text');
79 }
80- var text_node = this.get('contentBox').one('.banner-text');
81 text_node.set('text', new_text);
82 }
83
84 }, {
85 ATTRS: {
86- notification_text: { value: "" },
87 banner_icon: { value: "<span></span>" },
88 banner_template: {
89 valueFn: function() {
90@@ -134,9 +135,12 @@
91 "</div>"].join('');
92 }
93 },
94+ notification_text: { value: "" },
95 skip_animation: { value: false },
96 visible: { value: false }
97 }
98 });
99
100-}, '0.1', {'requires': ['base', 'node', 'anim', 'widget', 'lp.mustache']});
101+}, '0.1', {
102+ requires: ['base', 'node', 'anim', 'widget', 'lp.mustache']
103+});
104
105=== modified file 'lib/lp/app/javascript/banners/beta-notification.js'
106--- lib/lp/app/javascript/banners/beta-notification.js 2012-05-23 21:28:36 +0000
107+++ lib/lp/app/javascript/banners/beta-notification.js 2012-09-26 13:33:25 +0000
108@@ -1,9 +1,49 @@
109+/**
110+ * Add a BetaBanner widget for use.
111+ *
112+ * @namespace lp.app.banner
113+ * @module beta
114+ *
115+ */
116 YUI.add('lp.app.banner.beta', function(Y) {
117
118 var ns = Y.namespace('lp.app.banner.beta');
119-var baseBanner = Y.lp.app.banner.Banner;
120-
121-ns.BetaBanner = Y.Base.create('betaBanner', baseBanner, [], {
122+var Banner = Y.lp.app.banner.Banner;
123+
124+// For the beta banner to work, it needs to have one instance, and one
125+// instance only.
126+window._singleton_beta_banner = null;
127+
128+ns.show_beta_if_needed = function () {
129+ if (window._singleton_beta_banner === null) {
130+ var src = Y.one('.yui3-betabanner');
131+ window._singleton_beta_banner = new ns.BetaBanner({ srcNode: src });
132+ }
133+ if (window._singleton_beta_banner.get('features').length !== 0) {
134+ window._singleton_beta_banner.render();
135+ window._singleton_beta_banner.show();
136+ }
137+};
138+
139+
140+/**
141+ * Banner to display for beta features.
142+ *
143+ * @class BetaBanner
144+ * @extends Banner
145+ *
146+ */
147+ns.BetaBanner = Y.Base.create('betaBanner', Banner, [], {
148+
149+ bindUI: function () {
150+ Banner.prototype.bindUI.apply(this, arguments);
151+ var close_box = Y.one('.global-notification-close');
152+ var that = this;
153+ close_box.on('click', function(e) {
154+ e.halt();
155+ that.hide();
156+ });
157+ },
158
159 renderUI: function () {
160 var banner_data = {
161@@ -20,35 +60,26 @@
162 '<a href="#" class="global-notification-close">Hide' +
163 '<span class="notification-close sprite" /></a>');
164 beta_node.appendChild(close_box);
165- },
166-
167- bindUI: function () {
168- baseBanner.prototype.bindUI.apply(this, arguments);
169- var close_box = Y.one('.global-notification-close');
170- var that = this;
171- close_box.on('click', function(e) {
172- e.halt();
173- that.hide();
174- })
175- },
176+ }
177
178 }, {
179 ATTRS: {
180- notification_text: { value: "Some parts of this page are in beta: " },
181 banner_icon: { value: '<span class="beta-warning">BETA!</span>' },
182+
183 banner_template: {
184 valueFn: function() {
185 return [
186- '<div class="global-notification transparent hidden">',
187+ '<div class="global-notification transparent hidden">',
188 '{{{ badge }}}',
189 '<span class="banner-text">',
190 '{{ text }}{{{ features }}}',
191 '</span>',
192- "</div>"].join('')
193- }
194+ "</div>"].join('');
195+ }
196 },
197+
198 features: {
199- valueFn: function () {
200+ valueFn: function () {
201 var features_template = [
202 '{{#features}}{{#is_beta}}',
203 '<span class="beta-feature"> {{title}}',
204@@ -59,26 +90,15 @@
205 '{{/is_beta}}{{/features}}'].join('');
206 var feature_data = {
207 features: Y.Object.values(LP.cache.related_features)
208- }
209+ };
210 return Y.lp.mustache.to_html(features_template, feature_data);
211 }
212 },
213+
214+ notification_text: { value: "Some parts of this page are in beta: " }
215 }
216 });
217
218-// For the beta banner to work, it needs to have one instance, and one
219-// instance only.
220-_singleton_beta_banner = null;
221-ns.show_beta_if_needed = function () {
222- if (_singleton_beta_banner === null) {
223- var src = Y.one('.yui3-betabanner')
224- _singleton_beta_banner = new ns.BetaBanner({ srcNode: src });
225- }
226- if (_singleton_beta_banner.get('features').length !== 0) {
227- _singleton_beta_banner.render();
228- _singleton_beta_banner.show();
229- }
230-}
231
232 }, '0.1', {'requires': ['base', 'node', 'anim', 'lp.mustache',
233 'lp.app.banner']});
234
235=== modified file 'lib/lp/app/javascript/banners/privacy.js'
236--- lib/lp/app/javascript/banners/privacy.js 2012-05-25 14:38:42 +0000
237+++ lib/lp/app/javascript/banners/privacy.js 2012-09-26 13:33:25 +0000
238@@ -1,33 +1,117 @@
239+/**
240+ * Add a PrivacyBanner widget for use.
241+ *
242+ * @namespace lp.app.banner
243+ * @module privacy
244+ *
245+ */
246 YUI.add('lp.app.banner.privacy', function(Y) {
247-
248 var ns = Y.namespace('lp.app.banner.privacy');
249-var baseBanner = Y.lp.app.banner.Banner;
250-
251-ns.PrivacyBanner = Y.Base.create('privacyBanner', baseBanner, [], {}, {
252- ATTRS: {
253- notification_text: {
254- value: "The information on this page is private."
255- },
256- banner_icon: {
257- value: '<span class="sprite notification-private"></span>'
258- }
259- }
260-});
261+var Banner = Y.lp.app.banner.Banner;
262+var info_type = Y.lp.app.information_type;
263+
264+ns.EV_SHOW = 'privacy_banner:show';
265+ns.EV_HIDE = 'privacy_banner:hide';
266+
267+/**
268+ * Allow for adjusting the global instance of the Privacy Banner via events.
269+ *
270+ * @event privacy_banner:show
271+ * @param text The message to show
272+ */
273+Y.publish(ns.EV_SHOW, {
274+ emitFacade: true
275+});
276+
277+/**
278+ * Hide the global instance of the banner via events.
279+ *
280+ * @event privacy_banner:hide
281+ */
282+Y.publish(ns.EV_HIDE, {
283+ emitFacade: true
284+});
285+
286
287 // For the privacy banner to work, it needs to have one instance, and one
288 // instance only.
289-var _singleton_privacy_banner = null;
290+window._singleton_privacy_banner = null;
291 ns.getPrivacyBanner = function (banner_text, skip_animation) {
292- if (_singleton_privacy_banner === null) {
293+ if (window._singleton_privacy_banner === null) {
294 var src = Y.one('.yui3-privacybanner');
295- _singleton_privacy_banner = new ns.PrivacyBanner(
296+ window._singleton_privacy_banner = new ns.PrivacyBanner(
297 { srcNode: src, skip_animation: skip_animation });
298- _singleton_privacy_banner.render();
299+ window._singleton_privacy_banner.render();
300 }
301 if (Y.Lang.isValue(banner_text)) {
302- _singleton_privacy_banner.updateText(banner_text);
303+ window._singleton_privacy_banner.updateText(banner_text);
304 }
305- return _singleton_privacy_banner;
306+ return window._singleton_privacy_banner;
307 };
308
309-}, "0.1", {"requires": ["base", "node", "anim", "lp.app.banner"]});
310+
311+/**
312+ * Banner to display when page contains private information.
313+ *
314+ * @class PrivacyBanner
315+ * @extends Banner
316+ *
317+ */
318+ns.PrivacyBanner = Y.Base.create('privacyBanner', Banner, [], {
319+ _custom_message: function (ev) {
320+ var body = Y.one('body');
321+ body.replaceClass('public', 'private');
322+ if (!ev.text) {
323+ thow('A custom privacy banner must have a text attribute');
324+ }
325+ this.updateText(ev.text);
326+ this.show();
327+ },
328+
329+ _make_public: function (ev) {
330+ body.replaceClass('private', 'public');
331+ this.hide();
332+ },
333+
334+ _make_private: function (ev) {
335+ // Update the text in the banner before we show it.
336+ debugger;
337+ var banner_text;
338+ var body = Y.one('body');
339+ body.replaceClass('public', 'private');
340+
341+ if (ev.text) {
342+ banner_text = ev.text;
343+ } else {
344+ banner_text = info_type.get_banner_text(ev.value);
345+ }
346+
347+ this.updateText(banner_text);
348+ this.show();
349+ },
350+
351+ bindUI: function () {
352+ that = this;
353+ Banner.prototype.bindUI.apply(this, arguments);
354+ Y.on(info_type.EV_ISPUBLIC, that._make_public, that);
355+ Y.on(info_type.EV_ISPRIVATE, that._make_private, that);
356+ Y.on(ns.EV_SHOW, that._custom_message, that);
357+ Y.on(ns.EV_HIDE, function (ev) {
358+ that.hide();
359+ }, that);
360+ },
361+
362+}, {
363+ ATTRS: {
364+ banner_icon: {
365+ value: '<span class="sprite notification-private"></span>'
366+ },
367+ notification_text: {
368+ value: "The information on this page is private."
369+ }
370+ }
371+});
372+
373+}, "0.1", {
374+ requires: ["base", "node", "anim", "lp.app.banner", "lp.app.information_type"]
375+});
376
377=== modified file 'lib/lp/app/javascript/banners/tests/test_privacy.html'
378--- lib/lp/app/javascript/banners/tests/test_privacy.html 2012-05-15 14:39:36 +0000
379+++ lib/lp/app/javascript/banners/tests/test_privacy.html 2012-09-26 13:33:25 +0000
380@@ -29,6 +29,8 @@
381 src="../../../../../../build/js/lp/app/mustache.js"></script>
382 <script type="text/javascript"
383 src="../../../../../../build/js/lp/app/banners/banner.js"></script>
384+ <script type="text/javascript"
385+ src="../../../../../../build/js/lp/app/information_type.js"></script>
386
387 <!-- The module under test. -->
388 <script type="text/javascript" src="../privacy.js"></script>
389@@ -41,8 +43,5 @@
390 <ul id="suites">
391 <li>lp.app.banner.privacy.test</li>
392 </ul>
393-
394- <!-- The example markup required by the script to run. -->
395- <div id="maincontent"></div>
396 </body>
397 </html>
398
399=== modified file 'lib/lp/app/javascript/banners/tests/test_privacy.js'
400--- lib/lp/app/javascript/banners/tests/test_privacy.js 2012-06-25 17:39:07 +0000
401+++ lib/lp/app/javascript/banners/tests/test_privacy.js 2012-09-26 13:33:25 +0000
402@@ -15,19 +15,22 @@
403 var login_logout = Y.Node.create('<div></div>')
404 .addClass('login-logout');
405 main.appendChild(login_logout);
406+
407+ var banner_node = Y.Node.create('<div></div>')
408+ .addClass('yui3-privacybanner');
409+ main.appendChild(banner_node);
410 Y.one('body').appendChild(main);
411 },
412
413 tearDown: function () {
414 Y.one('#maincontent').remove(true);
415- Y.all('.yui3-banner').remove(true);
416+ window._singleton_privacy_banner = null;
417 },
418
419 test_library_exists: function () {
420 Y.Assert.isObject(Y.lp.app.banner.privacy,
421 "Could not locate the lp.app.banner.privacy module");
422- },
423-
424+ },
425 test_init: function () {
426 var banner = new Y.lp.app.banner.privacy.PrivacyBanner();
427 Y.Assert.areEqual(
428@@ -50,6 +53,32 @@
429 Y.Assert.areEqual(
430 new_text,
431 Y.one('.global-notification').get('text'));
432+ },
433+
434+ test_banner_with_custom_text: function () {
435+ var banner = Y.lp.app.banner.privacy.getPrivacyBanner();
436+ var new_text = 'New custom text';
437+
438+ Y.fire('privacy_banner:show', {
439+ text: new_text
440+ });
441+ Y.Assert.areEqual(
442+ new_text,
443+ Y.one('.global-notification').get('text'));
444+ Y.Assert.isTrue(banner.get('visible'),
445+ 'Banner should be visible.');
446+ },
447+
448+ test_banner_hide_event: function () {
449+ var banner = Y.lp.app.banner.privacy.getPrivacyBanner();
450+ var new_text = 'New custom text';
451+
452+ Y.fire('privacy_banner:show', {
453+ text: new_text
454+ });
455+ Y.fire('privacy_banner:hide');
456+ Y.Assert.isFalse(banner.get('visible'),
457+ 'Banner should not be visible.');
458 }
459 }));
460
461
462=== modified file 'lib/lp/app/javascript/information_type.js'
463--- lib/lp/app/javascript/information_type.js 2012-09-21 15:39:22 +0000
464+++ lib/lp/app/javascript/information_type.js 2012-09-26 13:33:25 +0000
465@@ -7,6 +7,69 @@
466 YUI.add('lp.app.information_type', function(Y) {
467
468 var ns = Y.namespace('lp.app.information_type');
469+ns.EV_CHANGE = 'information_type:change';
470+ns.EV_ISPUBLIC = 'information_type:is_public';
471+ns.EV_ISPRIVATE = 'information_type:is_private';
472+
473+/**
474+ * Any time the information type changes during a page we need an event we can
475+ * watch out for that.
476+ *
477+ * @event information_type:change
478+ * @param value The new information type value.
479+ */
480+Y.publish(ns.EV_CHANGE, {
481+ emitFacade: true
482+});
483+
484+/*
485+ * We also want shortcuts we can use to fire specific events if the new
486+ * information type is public or private (generally speaking).
487+ *
488+ * @event information_type:is_public
489+ * @param value The new information type value.
490+ */
491+Y.publish(ns.EV_ISPUBLIC, {
492+ emitFacade: true
493+});
494+
495+/**
496+ * The information type has been changed to a private type.
497+ *
498+ * @event information_type:is_private
499+ * @param value The new information type value.
500+ */
501+Y.publish(ns.EV_ISPRIVATE, {
502+ emitFacade: true
503+});
504+
505+/**
506+ * Wire up a helper event so that if someone changes the event, we take care
507+ * of also firing any is_private/is_public event shortcuts others want to
508+ * listen on.
509+ *
510+ * This enables us to keep the looking up of information type to ourselves
511+ * instead of having each module needing to know the location in the LP.cache
512+ * and such.
513+ */
514+Y.on(ns.EV_CHANGE, function (ev) {
515+ if (!ev.value) {
516+ throw('Information type change event without new value');
517+ }
518+
519+ if (ns.get_cache_data_from_key(ev.value,
520+ 'value',
521+ 'is_private')) {
522+ Y.fire(ns.EV_ISPRIVATE, {
523+ value: ev.value
524+ });
525+ } else {
526+ Y.fire(ns.EV_ISPUBLIC, {
527+ value: ev.value
528+ });
529+ }
530+});
531+
532
533 // For testing.
534 var skip_animation = false;
535
536=== modified file 'lib/lp/app/javascript/tests/test_information_type.js'
537--- lib/lp/app/javascript/tests/test_information_type.js 2012-09-21 15:39:22 +0000
538+++ lib/lp/app/javascript/tests/test_information_type.js 2012-09-26 13:33:25 +0000
539@@ -9,6 +9,12 @@
540 tests.suite.add(new Y.Test.Case({
541 name: 'lp.app.information_type_tests',
542
543+ _should: {
544+ error: {
545+ test_change_event_empty: true
546+ }
547+ },
548+
549 setUp: function() {
550 window.LP = {
551 cache: {
552@@ -315,6 +321,75 @@
553 Y.Assert.isTrue(summary.hasClass('public'));
554 // The error was displayed.
555 Y.Assert.isNotNull(Y.one('.yui3-lazr-formoverlay-errors'));
556+ },
557+
558+ test_change_event_empty: function () {
559+ // When firing a change event you must supply a value.
560+ Y.fire('information_type:change');
561+ },
562+
563+ test_change_event_fires: function () {
564+ var called = false;
565+ var change_ev = Y.on('information_type:change', function (ev) {
566+ Y.Assert.areEqual('PUBLIC', ev.value);
567+ called = true;
568+ });
569+
570+ Y.fire('information_type:change', {
571+ value: 'PUBLIC'
572+ });
573+
574+ Y.Assert.isTrue(called, 'Did get a called event');
575+
576+ // clean up our event since it's global to our Y instance.
577+ change_ev.detach();
578+ },
579+
580+ test_change_public_event: function () {
581+ // If the value is a public value then the is_public event fires.
582+ var called = false;
583+ var public_ev = Y.on('information_type:is_public', function (ev) {
584+ Y.Assert.areEqual('PUBLIC', ev.value);
585+ called = true;
586+ });
587+ // However is should not fire an is_private event.
588+ var private_ev = Y.on('information_type:is_private', function (ev) {
589+ called = false;
590+ });
591+
592+ Y.fire('information_type:change', {
593+ value: 'PUBLIC'
594+ });
595+
596+ Y.Assert.isTrue(called, 'Did get a called event');
597+
598+ // Clean up our event since it's global to our Y instance.
599+ public_ev.detach();
600+ private_ev.detach();
601+ },
602+
603+ test_change_private_event: function () {
604+ // If the value is a private value then the is_private event fires.
605+ var called = false;
606+
607+ // However is should not fire an is_private event.
608+ var public_ev = Y.on('information_type:is_public', function (ev) {
609+ called = false;
610+ });
611+ var private_ev = Y.on('information_type:is_private', function (ev) {
612+ Y.Assert.areEqual('PROPRIETARY', ev.value);
613+ called = true;
614+ });
615+
616+ Y.fire('information_type:change', {
617+ value: 'PROPRIETARY'
618+ });
619+
620+ Y.Assert.isTrue(called, 'Did get a called event');
621+
622+ // Clean up our event since it's global to our Y instance.
623+ public_ev.detach();
624+ private_ev.detach();
625 }
626 }));
627
628
629=== modified file 'lib/lp/blueprints/browser/specification.py'
630--- lib/lp/blueprints/browser/specification.py 2012-09-25 18:57:52 +0000
631+++ lib/lp/blueprints/browser/specification.py 2012-09-26 13:33:25 +0000
632@@ -123,10 +123,8 @@
633 from lp.blueprints.interfaces.sprintspecification import ISprintSpecification
634 from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
635 from lp.registry.interfaces.distribution import IDistribution
636-from lp.registry.interfaces.product import (
637- IProduct,
638- IProductSeries,
639- )
640+from lp.registry.interfaces.product import IProduct
641+from lp.registry.interfaces.productseries import IProductSeries
642 from lp.services.config import config
643 from lp.services.features import getFeatureFlag
644 from lp.services.fields import WorkItemsText
645
646=== modified file 'lib/lp/bugs/javascript/filebug.js'
647--- lib/lp/bugs/javascript/filebug.js 2012-09-21 15:39:22 +0000
648+++ lib/lp/bugs/javascript/filebug.js 2012-09-26 13:33:25 +0000
649@@ -97,8 +97,8 @@
650 Y.lp.app.choice.addPopupChoice(
651 'importance', LP.cache.bugtask_importance_data);
652 var cache = LP.cache.information_type_data;
653- var information_helpers = Y.lp.app.information_type
654- var choices = information_helpers.cache_to_choicesource(cache)
655+ var information_helpers = Y.lp.app.information_type;
656+ var choices = information_helpers.cache_to_choicesource(cache);
657
658 Y.lp.app.choice.addPopupChoiceForRadioButtons(
659 'information_type', choices, true);