Compare commits

...

180 Commits
1.3.1 ... 1.3.9

Author SHA1 Message Date
Tim Abbott
5fa6260ae8 Add changelog for Zulip 1.3.9 release. 2015-11-16 08:46:48 -08:00
Tim Abbott
7395003e6a Fix buggy #! lines using "/usr/bin/env python2.7 -u".
The #! line processing interpreted the argument to pass to `env` as
"python2.7 -u", which obviously isn't a real program.

We fix this by setting the PYTHONUNBUFFERED environment variable
inside the program, which has the same effect.

Thanks to Dan Fedele for the bug report and suggested solution!
2015-11-16 08:46:48 -08:00
Tim Abbott
ac35d26868 Add changelog for Zulip 1.3.8 release. 2015-11-15 14:58:52 -08:00
Tim Abbott
788b688935 Document upgrade process complications around puppet. 2015-11-15 14:58:52 -08:00
Tim Abbott
bd817fba97 Expand documentation on how to use the development environment.
* Reorganize to cover how to use the VM regardless of install process
  used.
* Document exactly what you need to do in order to see your changes.
* Remove the now-inaccurate documentation about flaky casper tests.
* Point to the testing documentation.
2015-11-15 14:31:40 -08:00
Tim Abbott
abdb148f42 Clarify instructions for setting up the S3 integration.
These instructions still aren't great due to #291, but at least this
is clear about how to get this working.
2015-11-15 13:50:12 -08:00
Tim Abbott
d06cb5d55a Expand documentation on writing and debugging Casper tests. 2015-11-11 21:36:00 -08:00
Tim Abbott
0cbedd6b0c Add casper tests for administration page user/bot (re/de)activation.
This should prevent future regressions like the one fixed in #243.
2015-11-11 21:35:55 -08:00
Tim Abbott
827babdf29 terminate-psql-sessions: Remove dependency on bc.
Fixes #281.
2015-11-11 21:35:16 -08:00
Tim Abbott
9d75fd33d9 Add new test for management commands running with --help.
This test caught a few bugs where refactoring had made management
commands fail (and would have caught a few more recent ones).

Ideally we'd replace this with a more advanced test that actually
tests that the management command do something useful, but it's a
start.
2015-11-11 21:34:39 -08:00
Tim Abbott
16ae985807 profile_request: Fix get_old_messages_backend import. 2015-11-11 21:34:39 -08:00
Tim Abbott
3e794351ab Remove expunge_* management commands.
These management commands never worked and given that we removed the
retention_policy code that they call, it makes sense to remove them as
well.
2015-11-11 21:34:39 -08:00
Tim Abbott
6718c85b13 Add troubleshooting documentation for the remote user SSO setup. 2015-11-11 21:33:52 -08:00
Tim Abbott
b81ecc4064 Add whitespace lint rules checking for missing spaces before {. 2015-11-10 10:06:47 -08:00
Tim Abbott
5f25974737 Fix Javascript whitespace issues with {. 2015-11-10 10:01:34 -08:00
Walter Heck
f145b01d91 Update create_user management command.
notify_new_user was recently moved to zerver.lib.actions from
zerver.views and this wasn't properly updated. This would give an
error when doing a `manage.py create_user` from the command line.
2015-11-08 19:41:00 -08:00
Tim Abbott
9b4c440e0d apps: Fix linking to SSO versions of desktop app.
The SSO build of the desktop app is intended only for those users who
who have settings.SSO_ONLY set, i.e. the only way to login is via the
site's SSO REMOTE_USER authentication.  We were incorrectly linking to
it on all production installations :(.
2015-11-06 09:51:25 -08:00
Tim Abbott
123f21c46b README.prod: Fix old requirements documentation. 2015-11-06 09:47:26 -08:00
Tim Abbott
2d0fbd068f Document how to revert to a previous version in upgrade process. 2015-11-06 09:45:41 -08:00
Tim Abbott
29706275df Document how to find the old realm if you've changed ADMIN_DOMAIN. 2015-11-06 09:45:41 -08:00
Tim Abbott
c3c4062ec7 Fix missing quotes in ADMIN_DOMAIN change docs. 2015-11-06 09:45:41 -08:00
Tim Abbott
61cb2ce883 Document how to clone the Zulip repository on Windows.
Fixes #272.
2015-11-06 09:45:41 -08:00
Tim Abbott
7c1b438ab0 Expand the Vagrant installation documentation significantly.
Thanks to Omar Farooq (o3dwade) for an early version of this!
2015-11-06 09:45:41 -08:00
Tim Abbott
5ffa2186d0 Merge common portions of the Fedora/CentOS development instructions.
There's still some parts of the CentOS 7 instructions that we may be
able to just delete.
2015-11-06 09:45:41 -08:00
Tim Abbott
0b77eb0291 Fix headings in installation instructions. 2015-11-06 09:45:41 -08:00
Tim Abbott
55ddc35b90 Move development installations instructions to a separate file.
This will make the core README.md file cleaner.
2015-11-06 09:45:41 -08:00
Tim Abbott
8181a0423f Fix some small issues with the CentOS/Fedora instructions. 2015-11-06 09:45:41 -08:00
Марко М. Костић (Marko M. Kostić)
7f4b82af18 Added CentOS 7 specific instructions to the manual install guide. 2015-11-06 09:45:41 -08:00
Allie Jones
85809e6140 Add webpack build process. 2015-11-06 09:13:25 -08:00
Allie Jones
e20a1bc73c Remove the committed handlebars package and install it via npm. 2015-11-06 09:09:41 -08:00
Allie Jones
4de0325a9d Install node dependencies using npm.
The node packages 'jQuery' and 'jquery' are different--'jQuery' is the
legacy support package that is needed for Zulip so the require statements
in the tests were updated.

Travis uses node 4.0 by default and we are using 0.10, so the command to
install the correct version had to be added to the .travis.yml file.
2015-11-06 09:08:59 -08:00
Allie Jones
46e267f4dc Add node-legacy package.
Some dependencies aren't configured to find the node binary correctly on
Debian (since it is called nodejs instead of node). The node-legacy package
fixes this.
2015-11-06 09:08:50 -08:00
Tim Abbott
ae04744606 admin: Refactor to remove unnecessary selections of active_user_row. 2015-11-04 08:15:55 -08:00
Tim Abbott
958ada9f44 admin: Fix deactivating bot users.
The previous code was using the same codepath as for real users, which
was unfortunate in two ways:
* It hit the wrong endpoint on the server and thus failed
* It popped up the "remove a user prompt" which described a bunch of
  things not relevant to bots.
2015-11-04 08:03:28 -08:00
Tim Abbott
5161891fbd admin: Fix reactivating bot users.
Because the `owner` field had the class email, we were sending the
concatination of the user and owner email addresses as the email
address in the reactivate requests.

Fixes #243.
2015-11-04 08:03:17 -08:00
Tim Abbott
f6f8f1fe36 Use new-style classes consistently for Python 3 support.
Also add the fixer for this to our list of fixers we check.
2015-11-04 08:01:52 -08:00
Tim Abbott
f52ffa7923 travis: Add Python 3 compatibility test.
This tests whether a new patch introduces any regressions related to
any of the Python 3 compatibility fixers we've run in the past, so
that we can make continuous forward progress on our path towards
Python 3 compatibility.

This produces error output that looks like this:
"""
Testing for additions of Python 2 patterns we've removed as part of moving towards Python 3 compatibility.

Running Python 3 compatibility test lib2to3.fixes.fix_apply
Running Python 3 compatibility test lib2to3.fixes.fix_except
diff --git a/zerver/views/__init__.py b/zerver/views/__init__.py
index b5c0102..2defd46 100644
--- a/zerver/views/__init__.py
+++ b/zerver/views/__init__.py
@@ -296,7 +296,7 @@ def accounts_register(request):
                 do_activate_user(user_profile)
                 do_change_password(user_profile, password)
                 do_change_full_name(user_profile, full_name)
-            except UserProfile.DoesNotExist, e:
+            except UserProfile.DoesNotExist as e:
                 user_profile = do_create_user(email, password, realm, full_name, short_name,
                                               prereg_user=prereg_user,
                                               newsletter_data={"IP": request.META['REMOTE_ADDR']})

Python 3 compatibility error(s) detected!  See diff above for what you need to change.
"""
2015-11-04 08:00:25 -08:00
Shumbashi
123791bfdd Fix 'manage.py makemessages' errors.
Running 'manage.py makemessages' produced two errors previously.

Closes #265.
2015-11-04 07:39:48 -08:00
Ahmed Shibani
4f29cfee9e Mark strings for translation in templates/zerver
In order to enable internationalization support in Zulip, and to use
Django internationalization tools, all strings in Zulip frontend needs
to be marked for translation.
2015-11-03 23:06:31 -08:00
Ahmed Shibani
47d8d784a2 Add 'blocktrans' to tools/check-templates.
Running check-templates test fails when there are 'blocktrans' tags in
django templates. The fix is to add 'blocktrans' to
is_django_block_tag function in check-templates.
2015-11-03 08:06:48 -08:00
Tim Abbott
6eb670097c Expand testing done via Travis CI to cover production pipeline.
With this change, we are now testing the production static asset
pipeline and installation process in a new testing job (and also run
the frontend/backend tests separately).

This means that changes that break the Zulip static asset pipeline or
production installation process are more likely to fail tests.  The
testing is imperfect in that it does not have proper isolation -- we
build a complete Zulip development environment and then install a
Zulip production environment on top of it, so e.g. any apt
dependencies installed for Zulip development will still be available
for the Zulip production environment.  But, it's better than nothing!

A good v2 of this would be to have the production setup process just
install the minimum stuff needed to run `build-release-tarball` and
then uninstall it / clean it up so that we can do a more clear
production installation, but that's more work.
2015-11-01 18:11:39 -08:00
Tim Abbott
421560af21 postgres-init-db: Stop all services before recreating database. 2015-11-01 18:11:39 -08:00
Tim Abbott
3c31f9a2e3 Drop database users prior to DROP/CREATE database.
This fixes an annoying issue where one tries to rebuild the database,
and it fails due to there being existing connections.

The one thing that is potentially scary about this implementation is
that it means it's now a lot easier to accidentally drop your
production database by running the wrong script; might be worth adding
a "--force" flag controlling this behavior or something.

Thanks to Nemanja Stanarevic and Neeraj Wahi for prototypes of this
implementation!  They did most of the work and testing for this.
2015-11-01 18:11:39 -08:00
Tim Abbott
b7cd000af6 install: Check nginx configuration is valid.
It's better to fail here and have the user correct the issue than fail
later.
2015-11-01 18:06:59 -08:00
Tim Abbott
33295180a9 Apply Python 3 futurize transform libmodernize.fixes.fix_unicode_type. 2015-11-01 09:35:06 -08:00
Tim Abbott
607eedfc25 Apply Python 3 futurize transform libmodernize.fixes.fix_zip. 2015-11-01 09:35:06 -08:00
Tim Abbott
f7878a61e1 Apply Python 3 futurize transform libmodernize.fixes.fix_xrange_six. 2015-11-01 09:35:06 -08:00
Tim Abbott
5ffb4deb8d Apply Python 3 futurize transform libmodernize.fixes.fix_raise_six. 2015-11-01 09:35:05 -08:00
Tim Abbott
cd6f8e9191 Apply Python 3 futurize transform libmodernize.fixes.fix_map. 2015-11-01 09:35:05 -08:00
Tim Abbott
ffc900fe6e Apply Python 3 futurize transform libmodernize.fixes.fix_int_long_tuple. 2015-11-01 09:26:17 -08:00
Tim Abbott
3b185ad4de Apply Python 3 futurize transform libmodernize.fixes.fix_input_six. 2015-11-01 09:26:17 -08:00
Tim Abbott
2ea0663a4a Apply Python 3 futurize transform libmodernize.fixes.fix_imports_six. 2015-11-01 09:26:16 -08:00
Tim Abbott
b3ac668779 Apply Python 3 futurize transform libmodernize.fixes.fix_filter. 2015-11-01 09:26:16 -08:00
Tim Abbott
651b011514 Apply Python 3 futurize transform libmodernize.fixes.fix_basestring. 2015-11-01 09:26:16 -08:00
Tim Abbott
7e63842003 Apply Python 3 futurize transform libfuturize.fixes.fix_raise. 2015-11-01 09:26:16 -08:00
Tim Abbott
f3783fb4a1 Apply Python 3 futurize transform libfuturize.fixes.fix_print_with_import. 2015-11-01 09:26:16 -08:00
Tim Abbott
f97649b35c Apply Python 3 futurize transform libfuturize.fixes.fix_next_call. 2015-11-01 09:26:16 -08:00
Tim Abbott
9c66229456 Apply Python 3 futurize transform libfuturize.fixes.fix_absolute_import. 2015-11-01 09:26:16 -08:00
Tim Abbott
43abd83d1c Apply Python 3 futurize transform lib2to3.fixes.fix_ws_comma. 2015-11-01 09:26:14 -08:00
Tim Abbott
2b61c0203d Apply Python 3 futurize transform lib2to3.fixes.fix_repr. 2015-11-01 09:25:49 -08:00
Tim Abbott
daddf7c519 Apply Python 3 futurize transform lib2to3.fixes.fix_numliterals. 2015-11-01 09:25:49 -08:00
Tim Abbott
2398a370e2 Apply Python 3 futurize transform lib2to3.fixes.fix_ne. 2015-11-01 09:25:49 -08:00
Tim Abbott
06f6ee6566 Apply Python 3 futurize transform lib2to3.fixes.fix_idioms. 2015-11-01 09:25:47 -08:00
Tim Abbott
e9243d0f0b Apply Python 3 futurize transform lib2to3.fixes.fix_has_key. 2015-11-01 08:10:01 -08:00
Tim Abbott
5ce6a3c8f9 Apply Python 3 futurize transform lib2to3.fixes.fix_funcattrs. 2015-11-01 08:09:54 -08:00
Tim Abbott
8c34c40924 Apply Python 3 futurize transform lib2to3.fixes.fix_except. 2015-11-01 08:08:33 -08:00
Shane Kearns
6e3426fe10 python api: allow control over the server certificate verification
The --insecure option ("insecure=true" in .zuliprc) disables
verification entirely, similar to other tools like curl.

The --cert_bundle ("cert_bundle=<file>" in .zuliprc) allows
a file to be specified containing the CA certificates to verify
against.
When using self-signed certificates, the server's public key
can be used as the only cerificate in the file.

This change incidentally fixes an issue where the "site" parameter
in .zuliprc was ignored when specifying --user and --api-key on
the command line.

Fixes: #104
2015-10-31 21:20:34 -07:00
Kara McNair
97a2e70d2b Add passing tests for emails replying to missed personal messages
Covers both direct PMs and huddle message recipients.
2015-10-31 17:28:21 -07:00
Kara McNair
a8e5755c7b Add tests for processing emailed-in stream messages (success & fail)
Prior to adding reply-to-missed-message-email functionality, adding
automated tests for simpler case - incoming stream messages. Added
to new file test_email_mirror.py.
Also removed the "if not body" code from process_stream_message that
will never run because of an upstream ZulipEmailForwardError exception.
2015-10-31 17:26:23 -07:00
Tim Abbott
10657c1d53 Move node tests to node_tests/. 2015-10-28 10:11:47 -07:00
Tim Abbott
81f32f4aa1 casper: Rename frontend_tests/run to clarify it uses casper. 2015-10-28 10:11:47 -07:00
Tim Abbott
5aa89fd6af casper: Move common.js and test-credentials.js to casper-lib/. 2015-10-28 10:11:47 -07:00
Tim Abbott
01f0d362d9 Move test_credentials.js into casper_tests/. 2015-10-28 10:11:47 -07:00
Tim Abbott
2294063361 Move casper server.log into casper_tests/. 2015-10-28 10:11:47 -07:00
Tim Abbott
988a9acead Move casper tests to a clearer directory name. 2015-10-28 10:11:47 -07:00
Tim Abbott
f1074aa491 Move frontend tests out of zerver/tests/.
This fixes an unfortunate bug where the backend tests in
zerver/tests.py were not being run automatically, and also makes these
a bit easier to find.
2015-10-28 10:11:47 -07:00
Tim Abbott
a36ac151ef Fix newly invited users receiving private stream history.
Also add a test to avoid this regressing in the future.

Fixes #230.
2015-10-26 23:36:37 -07:00
Tim Abbott
c1686235cd Fix construction of names in LDAP integration.
Previously these users' names were being set to 1-element lists
containing the name, not the names themselves.  This bug caused
existing users to have their people module state (e.g. @-mentions,
etc.) to break whenever a new user joined.

Fixes #222.
2015-10-26 22:49:10 -07:00
Kara McNair
8e429759e2 Replace 'flaky' and 'freaking out' with less personified words.
The tests to recognize a misbehaving/unpredictable worker task use
the words 'flaky' and 'freaking out' in personifying the system
behavior. This terminology isn't inclusive of people with
mental health issues or mood disorders, so this change updates
the wording to have less personification and more objective system
description. (http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1925070/)
2015-10-26 20:14:48 -07:00
Gautam Kotian
fb5192e85b Fix typo in README.md. 2015-10-26 20:04:55 -07:00
Elizabeth Sander
2ba01f4900 Defer permission for notifications until after tutorial.
In the process, remove old mozilla notifications hack since the
notifications_api shim should handle that case correctly.

Fixes #17.
2015-10-26 09:44:15 -07:00
Bernhard Morgenstern
dd2ccff22b Fix indentation for Trac ticket description changes.
Before this fix, the "to" was included in the markdown blocks.
2015-10-22 15:02:35 -07:00
Steven Oud
d5435fad1d Consistently use /usr/bin/env python2.7 in shebangs and commands. 2015-10-21 22:58:21 +00:00
Tim Abbott
136c55e43d Document new zulip-help@ mailing list for installation help. 2015-10-19 21:45:44 -07:00
Tim Abbott
b76e78c4dd Expand documentation on contributing to Zulip. 2015-10-19 21:01:18 -07:00
Tim Abbott
3bb0ce2383 Changelog: Simplify formatting and document 1.3.7 release. 2015-10-19 20:32:47 -07:00
Tim Abbott
54c964a332 Rewrite the email gateway integration instructions. 2015-10-19 10:10:20 -07:00
Tim Abbott
a6ddd28c9e Clarify the steps in the outgoing SMTP setup process. 2015-10-19 10:09:45 -07:00
Tim Abbott
494797ea0a Fix has_valid_realm logic following get_realm refactor. 2015-10-19 09:59:06 -07:00
Tim Abbott
3e1f4e611c Clarify on zulip.com signup form that we're not taking new teams. 2015-10-19 09:37:24 -07:00
Tim Abbott
758baca01a run-dev.py: Report a nice error if you run it as root.
Fixes #172.
2015-10-15 12:45:38 -04:00
Tim Abbott
3167b64d1c Extend changelog with other unreleased improvemenets since 1.3.6. 2015-10-15 12:31:28 -04:00
Nicholas Bergson-Shilcock
89a2765553 Turn off desktop notifications by default for new users.
New users will no longer get desktop and audible notifications for all streams
by default.

This also updates the `day1` follow-up email to let users know they can
customize how and when Zulip notifies them of new messages.

Lastly, this adds a `changelog.md` file, following the conventions from
keepachangelog.com, to track changes for new releases.
2015-10-15 12:25:32 -04:00
Tim Abbott
e75ba630fb initialize-database: Make management command errors fatal again.
We accidentally made this non-fatal when we added the nice error
output telling users to run postgres-init-db.
2015-10-15 12:21:46 -04:00
Tim Abbott
bf694fa832 Flush memcached whenever we drop the databases.
This fixes some issues that we've had where commands will fail is
confusing ways after the database is rebuilt because data from before
the database was dropped is still in the memcached cache.
2015-10-15 12:18:41 -04:00
Tim Abbott
32aea4c9dd Fix get_unique_open_realm always returning None in production.
Fixes #186.
2015-10-15 10:21:04 -04:00
Tim Abbott
5d22f5ee0a Improve LDAP_APPEND_DOMAIN default.
The documentation suggests the default is None; this change makes that
true.  Also make the actual code robust to this being set to "" instead.
2015-10-15 09:16:59 -04:00
Tim Abbott
71a06d58de Convert uses of Realm.objects.get() to get_realm().
get_realm is better in two key ways:
* It uses memcached to fetch the data from the cache and thus is faster.
* It does a case-insensitive query and thus is more safe.
2015-10-15 09:16:58 -04:00
Tim Abbott
51ed5028dc Remove unnecessary get_realm_name function. 2015-10-15 09:16:58 -04:00
Tim Abbott
419d31a007 Expand documentation for the LDAP auth integration.
Fixes #134, #173.
2015-10-15 09:16:58 -04:00
Tim Abbott
784ba7e066 Fix support for LDAP Authentication mechanism.
This addresses a few issues:
* The LDAP authentication integration now creates an account a new
  Zulip account if the user authenticated correctly but didn't have a
  Zulip account.
* The previous code didn't correctly disable the LDAP group
  permissions functionality.  We're not using groups support from the
  Django LDAP extension and not doing so can cause errors trying to
  fetch data from LDAP.

Huge thanks to @toaomatis for the initial implementation of this.

Fixes #72.
2015-10-15 09:16:58 -04:00
Tim Abbott
90e61d3b61 Call process_new_human_user consistently when creating new users.
Previously we only did this when new human users were created via the
login process, which meant the management command to create a user did
not add the user to default streams (for example) and any future code
that might want to register a new Zulip user (such as the LDAP
integration) would need to import views/__init__.py in order to
properly set this up.
2015-10-15 09:16:58 -04:00
Tim Abbott
355e1bbd94 Move process_new_human_user and helpers from views to actions.py. 2015-10-15 09:16:58 -04:00
Tim Abbott
792075ddeb sync_api_key: Don't throw a messy exception when user doesn't exist. 2015-10-15 09:16:58 -04:00
Tim Abbott
3e735d36d1 Rename tools/postgres-init-db to tools/postgres-init-dev-db.
The previous name was confusing because we also have
scripts/setup/postgres-init-db.
2015-10-15 09:14:21 -04:00
Allie Jones
99a2ba38b1 Expand new feature tutorial. 2015-10-15 09:12:22 -04:00
Tim Abbott
e8e38e911b Fix casperjs tests in Travis CI.
For reasons I don't understand, it appears that in Travis CI we're now
seeing errors using Casper that seem to correspond to a compatibility
issue introduced in PhantomJS 2, even though we're still using 1.9.8.

The solution for that compatability issue of patching casper's
bootstrap.js to get arguments from system.args at a slightly different
time than before seems to work in our setting as well, and that's what
this implements.

Probably the right long-term solution involves upgrading both
phantomjs and Casper to the latest versions.
2015-10-14 21:49:09 -04:00
Tim Abbott
eac6ea75dd emoji_dump: Exit with nonzero status when there are failures.
Previously, emoji_dump would happily exit successfully even if it
wasn't able to generate all the emoji.
2015-10-14 21:48:13 -04:00
Tim Abbott
5ee50cdced Install libfreetype6-dev in the development environment.
This fixes a problem where the emoji_dump tool was not generating the
black-and-white emoji.  The issue is that Pillow compiled without
libfreetype cannot extract those emoji (and gives an error of the form
"The _imagingft C module is not installed"), and if libfreetype-dev
isn't installed, pip will happily build and install Pillow without
libfreetype.
2015-10-14 18:58:36 -04:00
Tim Abbott
1dc09f3abd Document need to restart run-dev.py when developing queue workers. 2015-10-14 16:11:04 -04:00
Tim Abbott
4309f92062 Pass worker errors through to the run-dev.py console. 2015-10-14 16:08:32 -04:00
Kara McNair
d72f75a7e1 email-mirror: Support missed message email token string format.
The do_send_missedmessage_events_reply_in_zulip function in the email
mirror didn't support EMAIL_GATEWAY_PATTERN that wasn't of the form
%s@example.com (which resulted in replies to missed message emails failing
to be parsed).
2015-10-14 16:02:15 -04:00
Tim Abbott
0d85ab2062 README: Clean up Mac installation instructions for Virtualbox/Vagrant.
* Removes the hardcoding of an old version of Virtualbox (and doesn't
  specify the version to avoid getting stale again over time).
* Flips around the langauge to assume you don't have Vagrant already.
* Makes clear that the first-time installation is a lot slower than
  future runs will be.

Fixes #5.
2015-10-14 12:05:23 -04:00
Tim Abbott
c6761e8604 provision.py: Check whether git repository is present.
Fixes #148.
2015-10-14 10:18:59 -04:00
Tim Abbott
6569018de7 Disable apt caching in Travis CI configuration.
Apparently it isn't supposed to work reliably with the container-based
infrastructure that we're using and empirically it's causing build
failures.

Thanks to @mijime for tracking this down.
2015-10-14 10:04:27 -04:00
Andrew Drozdov
f6311478e6 Add docs on how to update default streams. 2015-10-13 11:09:37 -04:00
David Farrell
59dfec8f8b Add Fedora-specific instructions to the manual install guide. 2015-10-07 15:23:59 -04:00
Raphael
0608e32eeb Cause install to return 1 on failure.
This fixes issue #123. Namely, the script in scripts/setup/install was
returning 0. Adding `set -e` and `set -o pipeline` causes the install
script to exit and return 1 if any part fails, including piping output
(`set -o pipeline` does this).
2015-10-07 08:46:16 -04:00
Darius Bacon
741e9d00d8 Install zulip_english.stop in 'By hand' install instructions.
This step was previously only present in provision.py.
2015-10-06 23:00:18 -04:00
Darren Worrall
77fad7a16e Add an api endpoint to fetch GOOGLE_CLIENT_ID
Further to #102, this provides an endpoint suitable for mobile apps to
consume the GOOGLE_CLIENT_ID if configured.
2015-10-06 23:28:08 +00:00
Raphael
ea65715ef8 manage.py: Give a nice error message if run as root on posix systems.
If the os is posix, this will check to see if the user is root and
alert them to run as zulip user if so.

Fixes #114.
2015-10-05 21:41:35 -04:00
David Farrell
e4cea98ccd Correct the filepath for rebuild test database script. 2015-10-05 21:28:54 -04:00
Luna Lunapiena
0ec99a0838 README: Add explicit instruction to clone Zulip repository. 2015-10-05 21:27:08 -04:00
David Farrell
411531ecaf Update script paths in by-hand instructions to execute as written. 2015-10-05 20:36:07 -04:00
Nicholas Bergson-Shilcock
759ab33981 Add note about installing Vagrant on Mac to README. 2015-10-05 20:34:27 -04:00
Justin Valentini
d490779307 Add a relative link to production README. 2015-10-04 16:28:36 +00:00
Luke Faraone
e014b68b84 Include license text in THIRDPARTY 2015-10-03 15:44:13 +00:00
Darren Worrall
14389145cd Make twitter settings validation test more explicit 2015-10-02 12:01:25 +01:00
Tim Abbott
a65656dd9d Fix backwards-compatibility for old python-requests .json property.
In b59b5cac35, we upgraded our Google
Oauth code to support new python-requests, but because Ubuntu precise
still has old python-requests, this broke the codepath for older
systems.
2015-10-01 18:54:17 -07:00
Tim Abbott
8e0479e7a0 Clarify Zulip upgrade process instructions. 2015-10-01 12:56:18 -07:00
Tim Abbott
ea7e5527be README.prod.md: Expand documentation on filling in settings.py. 2015-10-01 12:42:36 -07:00
Tim Abbott
feca065dd8 README.prod.md: Improve instructions for downloading server tarball. 2015-10-01 12:42:35 -07:00
Liam Marshall
a822118dcb Fix a bunch of formatting issues in README.prod.md.
Removes:
* Several unused <hr>s

Fixes:
* Odd linebreaks
* Inconsistent headers
* URLs which should be links
* Headers which should be headers

Code-formats:
* envvars
* FQDNs
* commands and command options
* config options
* code
2015-10-01 12:42:16 -07:00
Allie Jones
cd1fa6a42e Fix notifications in Firefox by calling the constructor with 'new'. 2015-10-01 09:31:05 -07:00
Tim Abbott
ad75959b92 README.prod: Add documentation for how to create an SSL certificate. 2015-10-01 09:31:05 -07:00
mijime
2db2fcea18 Fix a puppet config to use SSO.
puppet::enterprise was renamed to puppet::voyager.
2015-10-01 09:31:05 -07:00
Darren Worrall
8b002040e0 Correct twitter library in requirements.
This also requires updating the required version of oauthlib; previously an
appropriate version was being installed only because it was a dependency of
the wrong twitter library.

This only affects development environments and/or hand-built
installations relying on the contents of requirements.txt.

To fix existing environments, the incorrect api needs to be explicitly
removed with `pip uninstall twitter`.

Fixes #86.
2015-09-30 13:49:33 -07:00
Ged Lawrenson
21b7048e54 install: Verify that the script has sufficient privileges. 2015-09-30 10:55:49 -07:00
Darren Worrall
bec3c0943a Fix validation that twitter creds are present.
They are looked up as secrets which initialize to `None`, but the code
was checking for empty strings.

This, along with #80, fixes #81.
2015-09-30 09:27:37 -07:00
Jason Michalski
7352f31c4b Add documentation for the pagerduty integrations
Add pagerduty to the list of supported integrations and walks users
through the setup process.

Fixes #36
2015-09-30 09:24:00 -07:00
Jason Michalski
dafe69761e Use stock emoji in the pagerduty integration
The pagerduty integration was using realm emoji. Use stock replacements
in the open source release.
2015-09-30 09:23:59 -07:00
Guillaume Simon
956fd7c420 puppet: Ensure rabbitmq-server and epmd services are running.
[tabbott@mit.edu: Added a few comments]
2015-09-30 09:21:45 -07:00
Tim Abbott
f819c1e901 Update the Zulip development documentation.
Fixes a few major issues:
* Documents RAM requirements for running Zulip development
* Fixes missing steps in the "by hand" installation process
* Improves the emphasis in the section no how to run tests on the common case.
* Documents that you can use LXC on newer Ubuntu as well.
2015-09-30 09:04:16 -07:00
Tim Abbott
3b00029c52 Show the username/password form if ZulipLDAPAuthBackend is enabled. 2015-09-30 09:04:16 -07:00
Tim Abbott
1482a386c2 Fix documentation for how to enable ZulipLDAPAuthBackend. 2015-09-30 09:04:16 -07:00
Tim Abbott
92aebe595b Dramatically extend post-install documentation for production Zulip. 2015-09-30 09:04:14 -07:00
Tim Abbott
5ad84fd997 Improve documentation for the Zulip email integration.
* Document fix for the 'less insecure' email problem.
* Mention that general Django email documentation applies.
2015-09-29 18:58:27 -07:00
Tim Abbott
40ec59b93e install: Add nice error message for RabbitMQ not having started. 2015-09-29 18:41:31 -07:00
Tim Abbott
5bf66e04fc initialize-database: Print nice instructions for how to redo if fails.
Most of our installation process is idempotent, but this step in
particular is not, so it's important to provide a clear error message
about how to proceed.
2015-09-29 18:27:27 -07:00
Tim Abbott
3efdb7ebf3 Document how to setup the Zulip S3 integration. 2015-09-29 18:11:58 -07:00
Tim Abbott
80fa5006f8 Document the purpose of local_settings.py properly. 2015-09-29 18:05:04 -07:00
Tim Abbott
bda9d78092 Use settings.ZULIP_ADMINISTRATOR as contact list for deactivated users. 2015-09-29 17:59:47 -07:00
Waseem Daher
6bb9b129f7 Update Zulip support email to zulip-devel@googlegroups.com.
Ideally some of these templates should really point to the
local installation's support email address, but this is a
good start.

Exceptions:
* Where to report security incidents
* MIT Zephyr-related pages
* zulip.com terms and conditions
2015-09-29 17:59:47 -07:00
Thomas Butter
d93d4c7216 Fix settings documentation of twitter keys.
Twitter keys are stored in zulip-secrets.conf.
2015-09-29 17:45:05 -07:00
Tim Abbott
852ac66f8e Extend the Google oauth documentation in local_server_template.py. 2015-09-28 10:05:58 -07:00
Amanpreet Singh
e20bc9f9b3 Fix "by hand" installation instructions.
- Add missing `python-dev` in apt-get install command
2015-09-28 09:24:55 -07:00
Tim Abbott
1f2f497cab Unrevert run Zulip tests automatically using Travis CI.
This contains a fix written by nemeth from PR #63 for doing argument
parsing properly.
2015-09-28 09:18:51 -07:00
Luke Faraone
578f769f60 Revert "Run Zulip tests automatically using Travis CI."
Improper list access from `sys.argv` would result in an exception if no
arguments are passed.

This reverts commit d2f5937d89.
2015-09-28 14:33:13 +00:00
Ian Whitlock
54fd321941 Add Vagrant-caused permissions problem to Possible Issues. 2015-09-27 17:48:07 -07:00
Tim Abbott
b6c1f1d162 Fix incorrect name for email_password secret in settings template.
Fixes #49.
2015-09-27 17:06:03 -07:00
Tim Abbott
d2f5937d89 Run Zulip tests automatically using Travis CI.
This is a bit hackish in that ideally we'd use proper options parsing
in provision.py, but it works and I even ran the tests 100x for tests
for flakes and didn't get any, so it's definitely an improvement!

With this we'll be both testing the runtime and effectively the Dev VM
setup process, which is awesome; the additional thing I'd want to add
tests for is the production setup process...
2015-09-27 16:29:20 -07:00
Caleb Anderson
ed742fa847 small typo fix 2015-09-27 01:10:01 -06:00
Tim Abbott
a625ca49ec puppet: Move /var/lib/nagios_state creation to zulip::base.pp.
Previously, in Zulip voyager, the cron jobs would spew error emails
every time they ran, due to this directory not existing.

This also tightens the permissions for the folder and avoids needing
to create a nagios user for Zulip voyager; it should be writeable by
both root and the zulip user and world-readable (and thus readable by
the Nagios user on zulip.com systems).
2015-09-26 21:44:23 -07:00
Tim Abbott
96bd1c38dc install: Make sure python is installed before using it.
This is relevant for completely bare Ubuntu systems which might only
have python3 installed.

Fixes #40.
2015-09-26 21:34:36 -07:00
Tim Abbott
9748780192 Remove unnecessary puppet.conf configuration.
Fixes #23.
2015-09-26 21:34:19 -07:00
Tim Abbott
bc3f096918 Update redis config to be supported on Trusty.
Previously our redis config was built for precise.

Synced from redis-server 2:2.8.4-2 plus our one change, which is
disabling saving to disk, so just put that at the bottom for maximum
obviousness.

I wish there was a better way to represent the fact that this is all
we're doing, since this will make life more difficult for running on
precise as well.

Fixes #28.
2015-09-26 21:33:55 -07:00
Tim Abbott
af4aac6836 settings: Document SMTP firewall issues in email configuration. 2015-09-26 21:32:47 -07:00
Tim Abbott
e5f7000a23 Add description of Zulip at top of README.md.
Based on PR #22 by GedLawrenson.
2015-09-26 18:07:06 -07:00
Tim Abbott
00bf7b25b5 install: Remote the python-django-guardian hack.
This is no longer required since it's in the PPA now.
2015-09-26 18:01:18 -07:00
Tim Abbott
2c6bfe136a Remove confusing EMAIL_HOST_PASSWORD setting in SMTP configuration.
This was one of the various configuration options made confusing by
the secrets-to-configuration migration.

Fixes #27.
2015-09-26 17:48:21 -07:00
Jon Johnson
db51a1c547 Fix typo in jabber mirror error
assistence -> assistance
2015-09-26 22:03:00 +00:00
Tim Abbott
3f76745235 Reorder README.md to highlight contributing section earlier. 2015-09-26 14:02:28 -07:00
Yuvi Panda
b59b5cac35 Fix TypeError in Google OAuth authenticator.
requests 1.0 changed response.json attribute to response.json()
instancemethod. The code wasn't updated to match that change,
causing a TypeError when attempting to use the Google OAuth
Authenticator backend.

This is fixed simply by using response.json() instead of response.json.
2015-09-26 13:51:27 -07:00
Tim Abbott
5dd330e769 Document how to setup Google auth for Zulip voyager. 2015-09-26 13:50:46 -07:00
Tim Abbott
140e598a89 Update configuration examples to use current Google auth backend. 2015-09-26 13:50:28 -07:00
Alice Pote
8159c03205 Add backticks around 'apt-get install'. 2015-09-26 12:13:21 -07:00
Anders Kaseorg
0d12dfd06f Improve shell quoting hygiene
Most of these problems were found by ShellCheck
(http://www.shellcheck.net).

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2015-09-25 23:25:08 -04:00
Josh Nichols
6888826d5b Link to settings page with absolute path, rather than explicitly zulip.com 2015-09-25 22:59:35 -04:00
Adam Smith
f0add4638c Update the puppet nginx file to match apache
this updates the nginx configuration file to match the installation documentation requirements and apache configuration file.
2015-09-25 19:34:58 -05:00
Leo Franchi
974a9bd0f3 Use -O option to wget when fetching zxcvbn
Otherwise the downloaded zxcvbn.js also includes garbage we don't want
2015-09-25 15:24:44 -07:00
706 changed files with 4853 additions and 38323 deletions

2
.gitattributes vendored
View File

@@ -15,6 +15,6 @@
/zproject/test_settings.py export-ignore
/zerver/fixtures export-ignore
/zerver/tests.py export-ignore
/zerver/tests export-ignore
/frontend_tests export-ignore
/node_modules export-ignore
/humbug export-ignore

3
.gitignore vendored
View File

@@ -13,7 +13,6 @@ stats/
zerver/fixtures/available-migrations
zerver/fixtures/migration-status
zerver/fixtures/test_data1.json
zerver/tests/frontend/test_credentials.js
.kdev4
zulip.kdev4
memcached_prefix
@@ -30,7 +29,9 @@ manage.log
event_queues.json
.vagrant
/zproject/dev-secrets.conf
static/js/bundle.js
static/third/gemoji/
static/third/zxcvbn/
tools/emoji_dump/bitmaps/
tools/emoji_dump/*.ttx
node_modules

22
.travis.yml Normal file
View File

@@ -0,0 +1,22 @@
before_install:
- nvm install 0.10
install:
- tools/travis/setup-$TEST_SUITE
cache:
- apt: false
env:
- TEST_SUITE=frontend
- TEST_SUITE=backend
- TEST_SUITE=production
- TEST_SUITE=py3k
language: python
python:
- "2.7"
# command to run tests
script:
- ./tools/travis/$TEST_SUITE
sudo: required
services:
- docker
addons:
postgresql: "9.3"

363
README.dev.md Normal file
View File

@@ -0,0 +1,363 @@
Installing the Zulip Development environment
============================================
You will need a machine with at least 2GB of RAM available (see
https://github.com/zulip/zulip/issues/32 for a plan for how to
dramatically reduce this requirement).
Start by cloning this repository: `git clone https://github.com/zulip/zulip.git`
Using Vagrant
-------------
This is the recommended approach for all platforms, and will install
the Zulip development environment inside a VM or container and works
on any platform that supports Vagrant.
The best performing way to run the Zulip development environment is
using an LXC container on a Linux host, but we support other platforms
such as Mac via Virtualbox (but everything will be 2-3x slower).
* If your host is Ubuntu 15.04 or newer, you can install and configure
the LXC Vagrant provider directly using apt:
```
sudo apt-get install vagrant lxc lxc-templates cgroup-lite redir
vagrant plugin install vagrant-lxc
```
* If your host is Ubuntu 14.04, you will need to [download a newer
version of Vagrant](https://www.vagrantup.com/downloads.html), and
then do the following:
```
sudo apt-get install lxc lxc-templates cgroup-lite redir
sudo dpkg -i vagrant*.deb # in directory where you downloaded vagrant
vagrant plugin install vagrant-lxc
```
* For other Linux hosts with a kernel above 3.12, [follow the Vagrant
LXC installation
instructions](https://github.com/fgrehm/vagrant-lxc) to get Vagrant
with LXC for your platform.
* If your host is OS X or older Linux, [download VirtualBox](https://www.virtualbox.org/wiki/Downloads),
[download Vagrant](https://www.vagrantup.com/downloads.html), and install them both.
* If you're on OS X and have VMWare, it should be possible to patch
Vagrantfile to use the VMWare vagrant provider which should perform
much better than Virtualbox. Patches to do this by default if
VMWare is available are welcome!
* On Windows: You can use Vagrant and Virtualbox/VMWare on Windows
with Cygwin, similar to the Mac setup. Be sure to create your git
clone using `git clone https://github.com/zulip/zulip.git -c
core.autocrlf=false` to avoid Windows line endings being added to
files (this causes weird errors).
Once that's done, simply change to your zulip directory and run
`vagrant up` in your terminal to install the development server. This
will take a long time on the first run because Vagrant needs to
download the Ubuntu Trusty base image, but later you can run `vagrant
destroy` and then `vagrant up` again to rebuild the environment and it
will be much faster.
Once that finishes, you can run the development server as follows:
```
vagrant ssh -- -L9991:localhost:9991
# Now inside the container
cd /srv/zulip
source /srv/zulip-venv/bin/activate
./tools/run-dev.py --interface=''
```
To get shell access to the virtual machine running the server to run
lint, management commands, etc., use `vagrant ssh`.
(A small note on tools/run-dev.py: the `--interface=''` option will make
the development server listen on all network interfaces. While this
is correct for the Vagrant guest sitting behind a NAT, you probably
don't want to use that option when using run-dev.py in other environments).
At this point you should [read about using the development environment](https://github.com/zulip/zulip/blob/master/README.dev.md#using-the-development-environment).
Using provision.py without Vagrant
----------------------------------
If you'd like to install a Zulip development environment on a server
that's already running Ubuntu 14.04 Trusty, you can do that by just
running:
```
sudo apt-get update
sudo apt-get install -y python-pbs
python /srv/zulip/provision.py
cd /srv/zulip
source /srv/zulip-venv/bin/activate
./tools/run-dev.py
```
Note that there is no supported uninstallation process without Vagrant
(with Vagrant, you can just do `vagrant destroy` to clean up the
development environment).
By hand
-------
If you really want to install everything by hand, the below
instructions should work.
Install the following non-Python dependencies:
* libffi-dev — needed for some Python extensions
* postgresql 9.1 or later — our database (also install development headers)
* memcached (and headers)
* rabbitmq-server
* libldap2-dev
* python-dev
* redis-server — rate limiting
* tsearch-extras — better text search
* libfreetype6-dev - needed before you pip install Pillow to properly generate emoji PNGs
### On Debian or Ubuntu systems:
```
sudo apt-get install libffi-dev memcached rabbitmq-server libldap2-dev python-dev redis-server postgresql-server-dev-all libmemcached-dev libfreetype6-dev
# If on 12.04 or wheezy:
sudo apt-get install postgresql-9.1
wget https://dl.dropboxusercontent.com/u/283158365/zuliposs/postgresql-9.1-tsearch-extras_0.1.2_amd64.deb
sudo dpkg -i postgresql-9.1-tsearch-extras_0.1.2_amd64.deb
# If on 14.04:
sudo apt-get install postgresql-9.3
wget https://dl.dropboxusercontent.com/u/283158365/zuliposs/postgresql-9.3-tsearch-extras_0.1.2_amd64.deb
sudo dpkg -i postgresql-9.3-tsearch-extras_0.1.2_amd64.deb
# If on 15.04 or jessie:
sudo apt-get install postgresql-9.4
wget https://dl.dropboxusercontent.com/u/283158365/zuliposs/postgresql-9.4-tsearch-extras_0.1_amd64.deb
sudo dpkg -i postgresql-9.4-tsearch-extras_0.1_amd64.deb
```
Now continue with the "All systems" instructions below.
### On Fedora 22 (experimental):
These instructions are experimental and may have bugs; patches welcome!
```
sudo dnf install libffi-devel memcached rabbitmq-server openldap-devel python-devel redis postgresql-server postgresql-devel postgresql libmemcached-devel freetype-devel
```
Now continue with the Common to Fedora/CentOS instructions below.
### On CentOS 7 Core (experimental):
These instructions are experimental and may have bugs; patches welcome!
```
# Add user zulip to the system (not necessary if you configured zulip as the administrator
# user during the install process of CentOS 7).
useradd zulip
# Create a password for zulip user
passwd zulip
# Allow zulip to sudo
visudo
# Add this line after line `root ALL=(ALL) ALL`
zulip ALL=(ALL) ALL
# Switch to zulip user
su zulip
# Enable EPEL 7 repo so we can install rabbitmq-server, redis and other dependencies
sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
# Install dependencies
sudo yum install libffi-devel memcached rabbitmq-server openldap-devel python-devel redis postgresql-server \
postgresql-devel postgresql libmemcached-devel wget python-pip openssl-devel freetype-devel libjpeg-turbo-devel \
zlib-devel nodejs
# We need these packages to compile tsearch-extras
sudo yum groupinstall "Development Tools"
# clone Zulip's git repo and cd into it
cd && git clone https://github.com/zulip/zulip && cd zulip/
## NEEDS TESTING: The next few DB setup items may not be required at all.
# Initialize the postgres db
sudo postgresql-setup initdb
# Edit the postgres settings:
sudo vi /var/lib/pgsql/data/pg_hba.conf
# Change these lines:
host all all 127.0.0.1/32 ident
host all all ::1/128 ident
# to this:
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
```
Now continue with the Common to Fedora/CentOS instructions below.
### Common to Fedora/CentOS instructions
```
# Build and install postgres tsearch-extras module
wget https://launchpad.net/~tabbott/+archive/ubuntu/zulip/+files/tsearch-extras_0.1.3.tar.gz
tar xvzf tsearch-extras_0.1.3.tar.gz
cd ts2
make
sudo make install
# Hack around missing dictionary files -- need to fix this to get
# the proper dictionaries from what in debian is the hunspell-en-us package.
sudo touch /usr/share/pgsql/tsearch_data/english.stop
sudo touch /usr/share/pgsql/tsearch_data/en_us.dict
sudo touch /usr/share/pgsql/tsearch_data/en_us.affix
# Edit the postgres settings:
sudo vi /var/lib/pgsql/data/pg_hba.conf
# Add this line before the first uncommented line to enable password auth:
host all all 127.0.0.1/32 md5
# Start the services
sudo systemctl start redis memcached rabbitmq-server postgresql
# Enable automatic service startup after the system startup
sudo systemctl enable redis rabbitmq-server memcached postgresql
```
Finally continue with the All Systems instructions below.
### All Systems:
```
pip install -r requirements.txt
./tools/download-zxcvbn
./tools/emoji_dump/build_emoji
./scripts/setup/generate_secrets.py -d
sudo cp ./puppet/zulip/files/postgresql/zulip_english.stop /usr/share/postgresql/9.3/tsearch_data/
./scripts/setup/configure-rabbitmq
./tools/postgres-init-dev-db
./tools/do-destroy-rebuild-database
./tools/postgres-init-test-db
./tools/do-destroy-rebuild-test-database
```
To start the development server:
```
./tools/run-dev.py
```
… and visit [http://localhost:9991/](http://localhost:9991/).
Using the Development Environment
=================================
Once the development environment is running, you can visit
<http://localhost:9991/> in your browser. By default, the development
server homepage just shows a list of the users that exist on the
server and you can login as any of them by just clicking on a user.
This setup saves time for the common case where you want to test
something other than the login process; to test the login process
you'll want to change AUTHENTICATION_BACKENDS in the not-PRODUCTION
case of `zproject/settings.py` from zproject.backends.DevAuthBackend
to use the auth method(s) you'd like to test.
While developing, it's helpful to watch the `run-dev.py` console
output, which will show any errors your Zulip development server
encounters.
When you make a change, here's a guide for what you need to do in
order to see your change take effect in Development:
* If you change Javascript or CSS, you'll just need to reload the
browser window to see changes take effect.
* If you change Python code used by the the main Django/Tornado server
processes, these services are run on top of Django's [manage.py
runserver](https://docs.djangoproject.com/en/1.8/ref/django-admin/#runserver-port-or-address-port),
which will automatically restart the Zulip Django and Tornado servers
whenever you save changes to Python code. You can watch this happen
in the `run-dev.py` console to make sure the backend has reloaded.
* The Python queue workers don't automatically restart when you save
changes (or when they stop running), so you will want to ctrl-C and
then restart `run-dev.py` manually if you are testing changes to the
queue workers or if a queue worker has crashed.
* If you change the database schema, you'll need to use the standard
Django migrations process to create and then run your migrations; see
the [new feature
tutorial](http://zulip.readthedocs.org/en/latest/new-feature-tutorial.html)
for an example. Additionally you should check out the [detailed
testing docs](http://zulip.readthedocs.org/en/latest/testing.html) for
how to run the tests properly after doing a migration.
(In production, everything runs under supervisord and thus will
restart if it crashes, and `upgrade-zulip` will take care of running
migrations and then cleanly restaring the server for you).
Running the test suite
======================
For more details, check out the [detailed testing
docs](http://zulip.readthedocs.org/en/latest/testing.html).
To run all the tests, do this:
```
./tools/test-all
```
For the Vagrant environment, you'll want to first enter the environment:
```
vagrant ssh
source /srv/zulip-venv/bin/activate
cd /srv/zulip
```
This runs the linter (`tools/lint-all`) plus all of our test suites;
they can all be run separately (just read `tools/test-all` to see
them). You can also run individual tests which can save you a lot of
time debugging a test failure, e.g.:
```
./tools/lint-all # Runs all the linters in parallel
./tools/test-backend zerver.test_bugdown.BugdownTest.test_inline_youtube
./tools/test-js-with-casper 10-navigation.js
./tools/test-js-with-node # Runs all node tests but is very fast
```
The above setup instructions include the first-time setup of test
databases, but you may need to rebuild the test database occasionally
if you're working on new database migrations. To do this, run:
```
./tools/postgres-init-test-db
./tools/do-destroy-rebuild-test-database
```
Possible testing issues
=======================
- When running the test suite, if you get an error like this:
```
sqlalchemy.exc.ProgrammingError: (ProgrammingError) function ts_match_locs_array(unknown, text, tsquery) does not exist
LINE 2: ...ECT message_id, flags, subject, rendered_content, ts_match_l...
^
```
… then you need to install tsearch-extras, described
above. Afterwards, re-run the `init*-db` and the
`do-destroy-rebuild*-database` scripts.
- When building the development environment using Vagrant and the LXC provider, if you encounter permissions errors, you may need to `chown -R 1000:$(whoami) /path/to/zulip` on the host before running `vagrant up` in order to ensure that the synced directory has the correct owner during provision. This issue will arise if you run `id username` on the host where `username` is the user running Vagrant and the output is anything but 1000.
This seems to be caused by Vagrant behavior; more information can be found here https://github.com/fgrehm/vagrant-lxc/wiki/FAQ#help-my-shared-folders-have-the-wrong-owner

277
README.md
View File

@@ -1,167 +1,130 @@
Zulip
=====
Zulip is a powerful, open source group chat application. Written in
Python and using the Django framework, Zulip supports both private
messaging and group chats via conversation streams.
Zulip also supports fast search, drag-and-drop file uploads, image
previews, group private messages, audible notifications,
missed-message emails, desktop apps, and much more.
Further information on the Zulip project and its features can be found
at https://www.zulip.org.
Installing the Zulip Development environment
============================================
Using Vagrant
-------------
This is the recommended approach, and is tested on OS X 10.10 as well as Ubuntu 14.04.
* If your host is OS X, download VirtualBox from
<http://download.virtualbox.org/virtualbox/4.3.30/VirtualBox-4.3.30-101610-OSX.dmg>
and install it.
* If your host is Ubuntu 14.04:
sudo apt-get install vagrant lxc lxc-templates cgroup-lite redir && vagrant plugin install vagrant-lxc
Once that's done, simply change to your zulip directory and run
`vagrant up` in your terminal. That will install the development
server inside a Vagrant guest.
Once that finishes, you can run the development server as follows:
```
vagrant ssh -- -L9991:localhost:9991
# Now inside the container
cd /srv/zulip
source /srv/zulip-venv/bin/activate
./tools/run-dev.py --interface=''
```
You can now visit <http://localhost:9991/> in your browser. To get
shell access to the virtual machine running the server, use `vagrant ssh`.
(A small note on tools/run-dev.py: the `--interface=''` option will make
the development server listen on all network interfaces. While this
is correct for the Vagrant guest sitting behind a NAT, you probably
don't want to use that option when using run-dev.py in other environments).
The run-dev.py console output will show any errors your Zulip
development server encounters. It runs on top of Django's "manage.py
runserver" tool, which will automatically restart the Zulip server
whenever you save changes to Python code.
By hand
-------
Install the following non-Python dependencies:
* libffi-dev — needed for some Python extensions
* postgresql 9.1 or later — our database (also install development headers)
* memcached (and headers)
* rabbitmq-server
* libldap2-dev
* python-dev
* redis-server — rate limiting
* tsearch-extras — better text search
On Debian or Ubuntu systems:
```
sudo apt-get install libffi-dev memcached rabbitmq-server libldap2-dev redis-server postgresql-server-dev-all libmemcached-dev
# If on 12.04 or wheezy:
sudo apt-get install postgresql-9.1
wget https://dl.dropboxusercontent.com/u/283158365/zuliposs/postgresql-9.1-tsearch-extras_0.1.2_amd64.deb
sudo dpkg -i postgresql-9.1-tsearch-extras_0.1.2_amd64.deb
# If on 14.04:
sudo apt-get install postgresql-9.3
wget https://dl.dropboxusercontent.com/u/283158365/zuliposs/postgresql-9.3-tsearch-extras_0.1.2_amd64.deb
sudo dpkg -i postgresql-9.3-tsearch-extras_0.1.2_amd64.deb
# If on 15.04 or jessie:
sudo apt-get install postgresql-9.4
wget https://dl.dropboxusercontent.com/u/283158365/zuliposs/postgresql-9.4-tsearch-extras_0.1_amd64.deb
sudo dpkg -i postgresql-9.4-tsearch-extras_0.1_amd64.deb
# Then, all versions:
pip install -r requirements.txt
./scripts/setup/configure-rabbitmq
./tools/postgres-init-db
./tools/do-destroy-rebuild-database
./tools/emoji_dump/build_emoji
```
To start the development server:
```
./tools/run-dev.py
```
… and hit http://localhost:9991/.
Running the test suite
======================
One-time setup of test databases:
```
./tools/postgres-init-test-db
./tools/do-destroy-rebuild-test-database
```
Run all tests:
```
./tools/test-all
```
This runs the linter plus all of our test suites; they can all be run
separately (just read `tools/test-all` to see them). You can also run
individual tests, e.g.:
```
./tools/test-backend zerver.test_bugdown.BugdownTest.test_inline_youtube
./tools/test-js-with-casper 10-navigation.js
```
Possible issues
===============
The Casper tests are flaky on the Virtualbox environment (probably due
to some performance-sensitive races). Until this issue is debugged,
you may need to rerun them to get them to pass.
When running the test suite, if you get an error like this:
```
sqlalchemy.exc.ProgrammingError: (ProgrammingError) function ts_match_locs_array(unknown, text, tsquery) does not exist
LINE 2: ...ECT message_id, flags, subject, rendered_content, ts_match_l...
^
```
… then you need to install tsearch-extras, described above. Afterwards, re-run the `init*-db` and the `do-destroy-rebuild*-database` scripts.
Contributing to Zulip
=====================
Zulip welcomes all forms of contributions!
Before a pull request can be merged, you need to to sign the [Dropbox
Contributor License Agreement](https://opensource.dropbox.com/cla/).
Please run the tests (tools/test-all) before submitting your pull
request.
Zulip has a growing collection of developer documentation including
detailed documentation on coding style available on [Read The
Docs](https://zulip.readthedocs.org/).
Zulip also has a [development discussion mailing list](https://groups.google.com/forum/#!forum/zulip-devel)
Feel free to send any questions or suggestions of areas where you'd
love to see more documentation to the list!
We recommend sending proposals for large features or refactorings to
the zulip-devel list for discussion and advice before getting too deep
into implementation.
Please report any security issues you discover to support@zulip.com.
The Zulip development environment is the recommened option for folks
interested in trying out Zulip. This is documented in
[README.dev.md](README.dev.md).
Running Zulip in production
===========================
This is documented in https://zulip.org/server.html and README.prod.md.
Zulip in production only supports Ubuntu 14.04 right now, but work is
ongoing on adding support for additional platforms. The installation
process is documented in https://zulip.org/server.html and in more
detail in [README.prod.md](README.prod.md).
Contributing to Zulip
=====================
Zulip welcomes all forms of contributions! The page documents the
Zulip development process.
* **Pull requests**. Before a pull request can be merged, you need to to sign the [Dropbox
Contributor License Agreement](https://opensource.dropbox.com/cla/).
Also, please skim our [commit message style
guidelines](http://zulip.readthedocs.org/en/latest/code-style.html#commit-messages).
* **Testing**. The Zulip automated tests all run automatically when
you submit a pull request, but you can also run them all in your
development environment following the instructions in the [testing
section](https://github.com/zulip/zulip#running-the-test-suite) below.
* **Developer Documentation**. Zulip has a growing collection of
developer documentation on [Read The Docs](https://zulip.readthedocs.org/).
Recommended reading for new contributors includes the
[directory structure](http://zulip.readthedocs.org/en/latest/directory-structure.html) and
[new feature tutorial](http://zulip.readthedocs.org/en/latest/new-feature-tutorial.html).
* **Mailing list and bug tracker** Zulip has a [development discussion
mailing list](https://groups.google.com/forum/#!forum/zulip-devel) and
uses [GitHub issues](https://github.com/zulip/zulip/issues). Feel
free to send any questions or suggestions of areas where you'd love to
see more documentation to the list! Please report any security issues
you discover to support@zulip.com.
* **App codebases** This repository is for the Zulip server and web app; the
[desktop](https://github.com/zulip/zulip-desktop),
[Android](https://github.com/zulip/zulip-android), and
[iOS](https://github.com/zulip/zulip-ios) apps are separate
repositories.
How to get involved with contributing to Zulip
==============================================
First, subscribe to the Zulip [development discussion mailing list](https://groups.google.com/forum/#!forum/zulip-devel).
The Zulip project uses a system of labels in our [issue
tracker](https://github.com/zulip/zulip/issues) to make it easy to
find a project if you don't have your own project idea in mind or want
to get some experience with working on Zulip before embarking on a
larger project you have in mind:
* [Bite Size](https://github.com/zulip/zulip/labels/bite%20size):
Smaller projects that could be a great first contribution.
* [Integrations](https://github.com/zulip/zulip/labels/integrations).
Integrate Zulip with another piece of software and contribute it
back to the community! Writing an integration can be a great
started project. There's some brief documentation on the best way
to write integrations at https://github.com/zulip/zulip/issues/70.
* [Documentation](https://github.com/zulip/zulip/labels/documentation).
The Zulip project loves contributions of new documentation.
* [Help Wanted](https://github.com/zulip/zulip/labels/help%20wanted):
A broader list of projects that nobody is currently working on.
* [Platform support](https://github.com/zulip/zulip/labels/Platform%20support).
These are open issues about making it possible to install Zulip on a wider
range of platforms.
* [Bugs](https://github.com/zulip/zulip/labels/bug). Open bugs.
* [Feature requests](https://github.com/zulip/zulip/labels/enhancement).
Browsing this list can be a great way to find feature ideas to implement that
other Zulip users are excited about.
If you're excited about helping with an open issue, just post on the
conversation thread that you're working on it. You're encouraged to
ask questions on how to best implement or debug your changes -- the
Zulip maintainers are excited to answer questions to help you stay
unblocked and working efficiently.
We also welcome suggestions of features that you feel would be
valuable or changes that you feel would make Zulip a better open
source project, and are happy to support you in adding new features or
other user experience improvements to Zulip.
If you have a new feature you'd like to add, we recommend you start by
opening a GitHub issue about the feature idea explaining the problem
that you're hoping to solve and that you're excited to work on it. A
Zulip maintainer will usually reply within a day with feedback on the
idea, notes on any important issues or concerns, and and often tips on
how to implement or test it. Please feel free to ping the thread if
you don't hear a response from the maintainers -- we try to be very
responsive so this usually means we missed your message.
For significant changes to the visual design, user experience, data
model, or architecture, we highly recommend posting a mockup,
screenshot, or description of what you have in mind to zulip-devel@ to
get broad feedback before you spend too much time on implementation
details.
Finally, before implementing a larger feature, we highly recommend
looking at the new feature tutorial and coding style guidelines on
ReadTheDocs.
Feedback on how to make this development process more efficient, fun,
and friendly to new contributors is very welcome! Just shoot an email
to the Zulip Developers list with your thoughts.
License
=======

View File

@@ -1,137 +1,510 @@
Zulip in production
===================
This documents the process for installing Zulip in a production environment.
Note that if you just want to play around with Zulip and see what it
looks like, it is easier to install it in a development environment
following the instructions in README.dev, since then you don't need to
worry about setting up SSL certificates and an authentication mechanism.
Recommended requirements:
* Server running Ubuntu Precise or Debian Wheezy
* At least 2 CPUs for production use
* At least 4GB of RAM for production use
* At least 100GB of free disk for production use
* HTTP(S) access to the public Internet (for some features;
discuss with Zulip Support if this is an issue for you)
* Server running Ubuntu Trusty
* At least 2 CPUs for production use with 100+ users
* At least 4GB of RAM for production use with 100+ users. We **strongly
recommend against installing with less than 2GB of RAM**, as you will
likely experience OOM issues. In the future we expect Zulip's RAM
requirements to decrease to support smaller installations (see
https://github.com/zulip/zulip/issues/32).
* At least 10GB of free disk for production use (more may be required
if you intend to store uploaded files locally rather than in S3
and your team uses that feature extensively)
* Outgoing HTTP(S) access to the public Internet.
* SSL Certificate for the host you're putting this on
(e.g. https://zulip.example.com)
* Email credentials for the service to send outgoing emails to users
(e.g. missed message notifications, password reminders if you're not
using SSO, etc.).
(e.g. zulip.example.com). If you just want to see what
Zulip looks like, we recommend installing the development
environment detailed in README.md as that is easier to setup.
* Email credentials Zulip can use to send outgoing emails to users
(e.g. email address confirmation emails during the signup process,
missed message notifications, password reminders if you're not using
SSO, etc.).
=======================================================================
How to install Zulip in production:
Installing Zulip in production
==============================
These instructions should be followed as root.
(1) Install the SSL certificates for your machine to
/etc/ssl/private/zulip.key
and
/etc/ssl/certs/zulip.combined-chain.crt
`/etc/ssl/private/zulip.key` and `/etc/ssl/certs/zulip.combined-chain.crt`.
If you don't know how to generate an SSL certificate, you, you can
do the following to generate a self-signed certificate:
(2) download zulip-server.tar.gz, and unpack to it /root/zulip, e.g.
tar -xf zulip-server-1.1.3.tar.gz
mv zulip-server-1.1.3 /root/zulip
```
apt-get install openssl
openssl genrsa -des3 -passout pass:x -out server.pass.key 4096
openssl rsa -passin pass:x -in server.pass.key -out zulip.key
rm server.pass.key
openssl req -new -key zulip.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey zulip.key -out zulip.combined-chain.crt
rm server.csr
cp zulip.key /etc/ssl/private/zulip.key
cp zulip.combined-chain.crt /etc/ssl/certs/zulip.combined-chain.crt
```
(3) run /root/zulip/scripts/setup/install
You will eventually want to get a properly signed certificate (and
note that at present the Zulip desktop app doesn't support
self-signed certificates), but this will let you finish the
installation process.
This may take a while to run, since it will install a large number of
packages via apt.
(2) Download [the latest built server tarball](https://www.zulip.com/dist/releases/zulip-server-latest.tar.gz)
and unpack it to `/root/zulip`, e.g.
```
wget https://www.zulip.com/dist/releases/zulip-server-latest.tar.gz
tar -xf zulip-server-latest.tar.gz
mv zulip-server-1.3.6 /root/zulip
```
(3) Run
```
/root/zulip/scripts/setup/install
```
This may take a while to run, since it will install a large number of
packages via apt.
(4) Configure the Zulip server instance by filling in the settings in
/etc/zulip/settings.py
`/etc/zulip/settings.py`. Be sure to fill in all the mandatory
settings, enable at least one authentication mechanism, and do the
configuration required for that authentication mechanism to work.
See the section on "Authentication" below for more detail on
configuring authentication mechanisms.
(5) su zulip -c /home/zulip/deployments/current/scripts/setup/initialize-database
(5) Run
```
su zulip -c /home/zulip/deployments/current/scripts/setup/initialize-database
```
This will report an error if you did not fill in all the mandatory
settings from `/etc/zulip/settings.py`. Once this completes
successfully, the main installation process will be complete, and if
you are planning on using password authentication, you should be able
to visit the URL for your server and register for an account.
This will report an error if you did not fill in all the mandatory
settings from /etc/zulip/settings.py. Once this completes
successfully, the main installation process will be complete, and if
you are planning on using password authentication, you should be able
to visit the URL for your server and register for an account.
(6) Subscribe to [the Zulip announcements Google Group](https://groups.google.com/forum/#!forum/zulip-announce)
to get announcements about new releases, security issues, etc.
(6) Subscribe to
https://groups.google.com/forum/#!forum/zulip-announce to get
announcements about new releases, security issues, etc.
=======================================================================
Authentication and logging into Zulip the first time
====================================================
Maintaining Zulip in production:
(As you read and follow the instructions in this section, if you run
into trouble, check out the troubleshooting advice in the next major
section.)
* To upgrade to a new version, download the appropriate release
tarball from https://www.zulip.org, and then run as root
Once you've finished installing Zulip, configuring your settings.py
file, and initializing the database, it's time to login to your new
installation. By default, initialize-database creates 1 realm that
you can join, the `ADMIN_DOMAIN` realm (defined in
`/etc/zulip/settings.py`).
/home/zulip/deployments/current/scripts/upgrade-zulip <tarball>
The `ADMIN_DOMAIN` realm is by default configured with the following settings:
* `restricted_to_domain=True`: Only people with emails ending with @ADMIN_DOMAIN can join.
* `invite_required=False`: An invitation is not required to join the realm.
* `invite_by_admin_only=False`: You don't need to be an admin user to invite other users.
* `mandatory_topics=False`: Users are not required to specify a topic when sending messages.
If you would like to change these settings, you can do so using the
Django management python shell (as the zulip user):
```
cd /home/zulip/deployments/current
./manage.py shell
from zerver.models import *
r = get_realm(settings.ADMIN_DOMAIN)
r.restricted_to_domain=False # Now anyone anywhere can login
r.save() # save to the database
```
If you realize you set `ADMIN_DOMAIN` wrong, in addition to fixing the
value in settings.py, you will also want to do a similar manage.py
process to set `r.domain = "newexample.com"`. If you've already
changed `ADMIN_DOMAIN` in settings.py, you can use
`Realm.objects.all()` in the management shell to find the list of
realms and pass the domain of the realm that is not "zulip.com" to
`get_realm`.
Depending what authentication backend you're planning to use, you will
need to do some additional setup documented in the `settings.py` template:
* For Google authentication, you need to follow the configuration
instructions around `GOOGLE_OAUTH2_CLIENT_ID` and `GOOGLE_CLIENT_ID`.
* For Email authentication, you will need to follow the configuration
instructions around outgoing SMTP from Django.
You should be able to login now. If you get an error, check
`/var/log/zulip/errors.log` for a traceback, and consult the next
section for advice on how to debug. If you aren't able to figure it
out, email zulip-help@googlegroups.com with the traceback and we'll
try to help you out!
You will likely want to make your own user account an admin user,
which you can do via the following management command:
```
./manage.py knight username@example.com -f
```
Now that you are an administrator, you will have a special
"Administration" tab linked to from the upper-right gear menu in the
Zulip app that lets you deactivate other users, manage streams, change
the Realm settings you may have edited using manage.py shell above,
etc.
You can also use `manage.py knight` with the
`--permission=api_super_user` argument to create API super users,
which are needed to mirror messages to streams from other users for
the IRC and Jabber mirroring integrations (see
`bots/irc-mirror.py` and `bots/jabber_mirror.py` for some detail on these).
There are a large number of useful management commands under
`zerver/manangement/commands/`; you can also see them listed using
`./manage.py` with no arguments.
One such command worth highlighting because it's a valuable feature
with no UI in the Administration page is `./manage.py realm_filters`,
which allows you to configure certain patterns in messages to be
automatically linkified, e.g. whenever someone mentions "T1234" it
could be auto-linkified to ticket 1234 in your team's Trac instance.
Checking Zulip is healthy and debugging the services it depends on
==================================================================
You can check if the zulip application is running using:
```
supervisorctl status
```
And checking for errors in the Zulip errors logs under
`/var/log/zulip/`. That contains one log file for each service, plus
`errors.log` (has all errors), `server.log` (logs from the Django and
Tornado servers), and `workers.log` (combined logs from the queue
workers).
After you change configuration in `/etc/zulip/settings.py` or fix a
misconfiguration, you will often want to restart the Zulip application.
You can restart Zulip using:
```
supervisorctl restart all
```
Similarly, you can stop Zulip using:
```
supervisorctl stop all
```
The Zulip application uses several major services to store and cache
data, queue messages, and otherwise support the Zulip application:
* postgresql
* rabbitmq-server
* nginx
* redis
* memcached
If one of these services is not installed or functioning correctly,
Zulip will not work. Below we detail some common configuration
problems and how to resolve them:
* An AMQPConnectionError traceback or error running rabbitmqctl
usually means that RabbitMQ is not running; to fix this, try:
```
service rabbitmq-server restart
```
If RabbitMQ fails to start, the problem is often that you are using
a virtual machine with broken DNS configuration; you can often
correct this by configuring `/etc/hosts` properly.
* If your browser reports no webserver is running, that is likely
because nginx is not configured properly and thus failed to start.
nginx will fail to start if you configured SSL incorrectly or did
not provide SSL certificates. To fix this, configure them properly
and then run:
```
service nginx restart
```
If you run into additional problems, [please report
them](https://github.com/zulip/zulip/issues) so that we can update
these lists! The Zulip installation scripts logs its full output to
`/var/log/zulip/install.log`, so please include the context for any
tracebacks from that log.
Making your Zulip instance awesome
==================================
Once you've got Zulip setup, you'll likely want to configure it the
way you like. There are four big things to focus on:
(1) Integrations. We recommend setting up integrations for the major
tools that your team works with. For example, if you're a software
development team, you may want to start with integrations for your
version control, issue tracker, CI system, and monitoring tools.
Spend time configuring these integrations to be how you like them --
if an integration is spammy, you may want to change it to not send
messages that nobody cares about (E.g. for the zulip.com trac
integration, some teams find they only want notifications when new
tickets are opened, commented on, or closed, and not every time
someone edits the metadata).
If Zulip doesn't have an integration you want, you can add your own!
Most integrations are very easy to write, and even more complex
integrations usually take less than a day's work to build. We very
much appreciate contributions of new integrations; there is a brief
draft integration writing guide [here](https://github.com/zulip/zulip/issues/70).
It can often be valuable to integrate your own internal processes to
send notifications into Zulip; e.g. notifications of new customer
signups, new error reports, or daily reports on the team's key
metrics; this can often spawn discussions in response to the data.
(2) Streams and Topics. If it feels like a stream has too much
traffic about a topic only of interest to some of the subscribers,
consider adding or renaming streams until you feel like your team is
working productively.
Second, most users are not used to topics. It can require a bit of
time for everyone to get used to topics and start benefitting from
them, but usually once a team is using them well, everyone ends up
enthusiastic about how much topics make life easier. Some tips on
using topics:
* When replying to an existing conversation thread, just click on the
message, or navigate to it with the arrow keys and hit "r" or
"enter" to reply on the same topic
* When you start a new conversation topic, even if it's related to the
previous conversation, type a new topic in the compose box
* You can edit topics to fix a thread that's already been started,
which can be helpful when onboarding new batches of users to the platform.
Third, setting default streams for new users is a great way to get
new users involved in conversations before they've accustomed
themselves with joining streams on their own. You can use the
[`set_default_streams`](https://github.com/zulip/zulip/blob/master/zerver/management/commands/set_default_streams.py)
command to set default streams for users within a realm:
```
python manage.py set_default_streams --domain=example.com --streams=foo,bar,...
```
(3) Notification settings. Zulip gives you a great deal of control
over which messages trigger desktop notifications; you can configure
these extensively in the `/#settings` page (get there from the gear
menu). If you find the desktop notifications annoying, consider
changing the settings to only trigger desktop notifications when you
receive a PM or are @-mentioned.
(4) The mobile and desktop apps. Currently, the Zulip Desktop app
only supports talking to servers with a properly signed SSL
certificate, so you may find that you get a blank screen when you
connect to a Zulip server using a self-signed certificate.
The Zulip iOS and Android apps in their respective stores don't yet
support talking to non-zulip.com servers; the iOS app is waiting on
Apple's app store review, while the Android app is waiting on someone
to do the small project of adding a field to specify what Zulip server
to talk to.
These issues will likely all be addressed in the coming weeks; make
sure to join the zulip-announce@googlegroups.com list so that you can
receive the announcements when these become available.
(5) All the other features: Hotkeys, emoji, search filters,
@-mentions, etc. Zulip has lots of great features, make sure your
team knows they exist and how to use them effectively.
(6) Enjoy your Zulip installation! If you discover things that you
wish had been documented, please contribute documentation suggestions
either via a GitHub issue or pull request; we love even small
contributions, and we'd love to make the Zulip documentation cover
everything anyone might want to know about running Zulip in
production.
Maintaining and upgrading Zulip in production
=============================================
We recommend reading this entire section before doing your first
upgrade.
* To upgrade to a new version of the zulip server, download the
appropriate release tarball from
https://www.zulip.com/dist/releases/ to a path readable by the zulip
user (e.g. /home/zulip), and then run as root:
```
/home/zulip/deployments/current/scripts/upgrade-zulip zulip-server-VERSION.tar.gz
```
Be sure to download to a path readable by the Zulip user (see
https://github.com/zulip/zulip/issues/208 for details on this
issue) but then run the upgrade as root.
The upgrade process will shut down the service, run `apt-get
upgrade` and any database migrations, and then bring the service
back up. This will result in some brief downtime for the service,
which should be under 30 seconds unless there is an expensive
transition involved. Unless you have tested the upgrade in advance,
we recommend doing upgrades at off hours.
upgrade`, a puppet apply, and any database migrations, and then
bring the service back up. This will result in some brief downtime
for the service, which should be under 30 seconds unless there is an
expensive transition involved. Unless you have tested the upgrade
in advance, we recommend doing upgrades at off hours.
You can create your own release tarballs from a copy of this
You can create your own release tarballs from a copy of zulip.git
repository using `tools/build-release-tarball`.
* To update your settings, simply edit /etc/zulip/settings.py and then
run /home/zulip/deployments/current/scripts/restart-server to
* **Warning**: If you have modified configuration files installed by
Zulip (e.g. the nginx configuration), the Zulip upgrade process will
overwrite your configuration when it does the `puppet apply`. You
can test whether this will happen assuming no upstream changes to
the configuration using `scripts/zulip-puppet-apply` (without the
`-f` option), which will do a test puppet run and output and changes
it would make. Using this list, you can save a copy of any files
that you've modified, do the upgrade, and then restore your
configuration. If you need to do this, please report the issue so
that we can make the Zulip puppet configuration flexible enough to
handle your setup.
* The Zulip upgrade script automatically logs output to
/var/log/zulip/upgrade.log; please use those logs to include output
that shows all errors in any bug reports.
* The Zulip upgrade process works by creating a new deployment under
/home/zulip/deployments/ containing a complete copy of the Zulip
server code, and then moving the symlinks at
`/home/zulip/deployments/current` and /root/zulip` as part of the
upgrade process. This means that if the new version isn't working,
you can quickly downgrade to the old version by using
`/home/zulip/deployments/<date>/scripts/restart-server` to return to
a previous version that you've deployed (the version is specified
via the path to the copy of `restart-server` you call).
* To update your settings, simply edit `/etc/zulip/settings.py` and then
run `/home/zulip/deployments/current/scripts/restart-server` to
restart the server
* You are responsible for running "apt-get upgrade" on your system on
* You are responsible for running `apt-get upgrade` on your system on
a regular basis to ensure that it is up to date with the latest
security patches.
* To use the Zulip API with your Zulip server, you will need to use the
API endpoint of e.g. "https://zulip.yourdomain.net/api". Our Python
API endpoint of e.g. `https://zulip.example.com/api`. Our Python
API example scripts support this via the
"--site=https://zulip.yourdomain.net" argument. The API bindings
support it via putting "site=https://zulip.yourdomain.net" in your
`--site=https://zulip.example.com` argument. The API bindings
support it via putting `site=https://zulip.example.com` in your
.zuliprc.
Every Zulip integration supports this sort of argument (or e.g. a
`ZULIP_SITE` variable in a zuliprc file or the environment), but this
is not yet documented for some of the integrations (the included
integration documentation on `/integrations` will properly document
how to do this for most integrations). Pull requests welcome to
document this for those integrations that don't discuss this!
* Similarly, you will need to instruct your users to specify the URL
for your Zulip server when using the Zulip desktop and mobile apps.
* As a measure to mitigate the impact of potential memory leaks in one
of the Zulip daemons, the service automatically restarts itself
every Sunday early morning. See /etc/cron.d/restart-zulip for the
every Sunday early morning. See `/etc/cron.d/restart-zulip` for the
precise configuration.
=======================================================================
SSO Authentication:
Remote User SSO Authentication
==============================
Zulip supports integrating with a corporate Single-Sign-On solution.
There are a few ways to do it, but this section documents how to
configure Zulip to use an SSO solution that best supports Apache and
will set the REMOTE_USER variable:
will set the `REMOTE_USER` variable:
(0) Check that /etc/zulip/settings.py has
"zproject.backends.ZulipRemoteUserBackend" as the only enabled value
in the "AUTHENTICATION_BACKENDS" list, and that "SSO_APPEND_DOMAIN" is
(0) Check that `/etc/zulip/settings.py` has
`zproject.backends.ZulipRemoteUserBackend` as the only enabled value
in the `AUTHENTICATION_BACKENDS` list, and that `SSO_APPEND_DOMAIN` is
correct set depending on whether your SSO system uses email addresses
or just usernames in REMOTE_USER.
or just usernames in `REMOTE_USER`.
Make sure that you've restarted the Zulip server since making this
configuration change.
(1) Edit /etc/zulip/zulip.conf and change the puppet_classes line to read:
(1) Edit `/etc/zulip/zulip.conf` and change the `puppet_classes` line to read:
puppet_classes = zulip::enterprise, zulip::apache_sso
(2) As root, run
/home/zulip/deployments/current/scripts/zulip-puppet-apply
```
puppet_classes = zulip::voyager, zulip::apache_sso
```
(2) As root, run `/home/zulip/deployments/current/scripts/zulip-puppet-apply`
to install our SSO integration.
(3) To configure our SSO integration, edit
/etc/apache2/sites-available/zulip-sso.example and fill in the
configuration required for your SSO service to set REMOTE_USER and
place your completed configuration file at
`/etc/apache2/sites-available/zulip-sso.example` and fill in the
configuration required for your SSO service to set `REMOTE_USER` and
place your completed configuration file at `/etc/apache2/sites-available/zulip-sso`
/etc/apache2/sites-available/zulip-sso
(4) Run `a2ensite zulip-sso` to enable the Apache integration site.
(4) Run
a2ensite zulip-sso
To enable the Apache integration site.
Now you should be able to visit https://zulip.yourdomain.net/ and
Now you should be able to visit `https://zulip.example.com/` and
login via the SSO solution.
### Troubleshooting Remote User SSO
This system is a little finicky to networking setup (e.g. common
issues have to do with /etc/hosts not mapping settings.EXTERNAL_HOST
to the Apache listening on 127.0.0.1/localhost, for example). It can
often help while debugging to temporarily change the Apache config in
/etc/apache2/sites-available/zulip-sso to listen on all interfaces
rather than just 127.0.0.1 as you debug this. It can also be helpful
to change /etc/nginx/zulip-include/app.d/external-sso.conf to
proxy_pass to a more explicit URL possibly not over HTTPS when
debugging. The following log files can be helpful when debugging this
setup:
* /var/log/zulip/{errors.log,server.log} (the usual places)
* /var/log/nginx/access.log (nginx access logs)
* /var/log/apache2/zulip_auth_access.log (you may want to change
LogLevel to "debug" in the apache config file to make this more
verbose)
Here's a summary of how the remote user SSO system works assuming
you're using HTTP basic auth; this summary should help with
understanding what's going on as you try to debug:
* Since you've configured /etc/zulip/settings.py to only define the
zproject.backends.ZulipRemoteUserBackend, zproject/settings.py
configures /accounts/login/sso as HOME_NOT_LOGGED_IN, which makes
`https://zulip.example.com/` aka the homepage for the main Zulip
Django app running behind nginx redirect to /accounts/login/sso if
you're not logged in.
* nginx proxies requests to /accounts/login/sso/ to an Apache instance
listening on localhost:8888 apache via the config in
/etc/nginx/zulip-include/app.d/external-sso.conf (using the upstream
localhost:8888 defined in /etc/nginx/zulip-include/upstreams).
* The Apache zulip-sso site which you've enabled listens on
localhost:8888 and presents the htpasswd dialogue; you provide
correct login information and the request reaches a second Zulip
Django app instance that is running behind Apache with with
REMOTE_USER set. That request is served by
`zerver.views.remote_user_sso`, which just checks the REMOTE_USER
variable and either logs in (sets a cookie) or registers the new
user (depending whether they have an account).
* After succeeding, that redirects the user back to / on port 443
(hosted by nginx); the main Zulip Django app sees the cookie and
proceeds to load the site homepage with them logged in (just as if
they'd logged in normally via username/password).
Again, most issues with this setup tend to be subtle issues with the
hostname/DNS side of the configuration. Suggestions for how to
improve this SSO setup documentation are very welcome!

View File

@@ -38,10 +38,6 @@ Files: confirmation/*
Copyright: 2008, Jarek Zgoda <jarek.zgoda@gmail.com>
License: BSD-3-Clause
Files: node_modules/handlebars/*
Copyright: 2011 Yehuda Katz
License: Expat
Files: puppet/apt/*
Copyright: 2011, Evolving Web Inc.
License: Expat
@@ -131,10 +127,6 @@ Copyright: Google, Inc.
License: Apache-2.0
Comment: These are actually Noto Emoji, not gemoji.
Files: static/third/handlebars/handlebars.runtime.js
Copyright: 2011 Yehuda Katz
License: Expat
Files: static/third/html5-formdata/formdata.js
Copyright: 2010 François de Metz
License: Expat
@@ -254,11 +246,325 @@ Files: zerver/lib/ccache.py
Copyright: 2013 David Benjamin and Alan Huang
License: Expat
Files: zerver/tests/frontend/casperjs/*
Files: frontend_tests/casperjs/*
Copyright: 2011-2012 Nicolas Perriault
Joyent, Inc. and other Node contributors
License: Expat
Files: zerver/tests/frontend/casperjs/modules/vendors/*
Files: frontend_tests/casperjs/modules/vendors/*
Copyright: 2011, Jeremy Ashkenas
License: Expat
License: Apache-2.0
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.
.
On Debian systems, the full text of the Apache License version 2 can
be found in /usr/share/common-licenses/Apache-2.0.
License: BSD-2-clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice(s), this list of conditions and the following disclaimer
unmodified other than the allowable addition of one or more
copyright notices.
2. Redistributions in binary form must reproduce the above copyright
notice(s), this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License: BSD-3-Clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
.
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License: CC-0-1.0
Creative Commons CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION
ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE
USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND
DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT
OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.
.
Statement of Purpose
.
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work
of authorship and/or a database (each, a "Work").
.
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without
fear of later claims of infringement build upon, modify, incorporate in
other works, reuse and redistribute as freely as possible in any form
whatsoever and for any purposes, including without limitation commercial
purposes. These owners may contribute to the Commons to promote the
ideal of a free culture and the further production of creative, cultural
and scientific works, or to gain reputation or greater distribution for
their Work in part through the use and efforts of others.
.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or
she is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under
its terms, with knowledge of his or her Copyright and Related Rights in
the Work and the meaning and intended legal effect of CC0 on those
rights.
.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
.
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
.
ii. moral rights retained by the original author(s) and/or performer(s);
.
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
.
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
.
v. rights protecting the extraction, dissemination, use and reuse of
data in a Work;
.
vi. database rights (such as those arising under Directive 96/9/EC of
the European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
.
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or
future medium and for any number of copies, and (iv) for any purpose
whatsoever, including without limitation commercial, advertising or
promotional purposes (the "Waiver"). Affirmer makes the Waiver for the
benefit of each member of the public at large and to the detriment of
Affirmer's heirs and successors, fully intending that such Waiver shall
not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of
the Work by the public as contemplated by Affirmer's express Statement
of Purpose.
.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non
exclusive, irrevocable and unconditional license to exercise Affirmer's
Copyright and Related Rights in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or
future medium and for any number of copies, and (iv) for any purpose
whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed
effective as of the date CC0 was applied by Affirmer to the Work. Should
any part of the License for any reason be judged legally invalid or
ineffective under applicable law, such partial invalidity or
ineffectiveness shall not invalidate the remainder of the License, and
in such case Affirmer hereby affirms that he or she will not (i)
exercise any of his or her remaining Copyright and Related Rights in the
Work or (ii) assert any associated claims and causes of action with
respect to the Work, in either case contrary to Affirmer's express
Statement of Purpose.
.
4. Limitations and Disclaimers.
.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied, statutory
or otherwise, including without limitation warranties of title,
merchantability, fitness for a particular purpose, non infringement, or
the absence of latent or other defects, accuracy, or the present or
absence of errors, whether or not discoverable, all to the greatest
extent permissible under applicable law.
.
c. Affirmer disclaims responsibility for clearing rights of other
persons that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the Work.
.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
License: Expat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
License: GPL-2.0
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2, dated June, 1991.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
On Debian systems, the complete text of the GNU General Public License
can be found in /usr/share/common-licenses/GPL-2 file.
License: SIL-OFL-1.1
---------------------------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
---------------------------------------------------------------------------
.
PREAMBLE
.
The goals of the Open Font License (OFL) are to stimulate worldwide development
of collaborative font projects, to support the font creation efforts of academic
and linguistic communities, and to provide a free and open framework in which
fonts may be shared and improved in partnership with others.
.
The OFL allows the licensed fonts to be used, studied, modified and redistributed
freely as long as they are not sold by themselves. The fonts, including any
derivative works, can be bundled, embedded, redistributed and/or sold with any
software provided that any reserved names are not used by derivative works. The
fonts and derivatives, however, cannot be released under any other type of license.
The requirement for fonts to remain under this license does not apply to any
document created using the fonts or their derivatives.
.
DEFINITIONS
.
"Font Software" refers to the set of files released by the Copyright Holder(s) under
this license and clearly marked as such. This may include source files, build
scripts and documentation.
.
"Reserved Font Name" refers to any names specified as such after the copyright
statement(s).
.
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
.
"Modified Version" refers to any derivative made by adding to, deleting, or
substituting -- in part or in whole -- any of the components of the Original Version,
by changing formats or by porting the Font Software to a new environment.
.
"Author" refers to any designer, engineer, programmer, technical writer or other
person who contributed to the Font Software.
.
PERMISSION & CONDITIONS
.
Permission is hereby granted, free of charge, to any person obtaining a copy of the
Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell
modified and unmodified copies of the Font Software, subject to the following
conditions:
.
1) Neither the Font Software nor any of its individual components, in Original or
Modified Versions, may be sold by itself.
.
2) Original or Modified Versions of the Font Software may be bundled, redistributed
and/or sold with any software, provided that each copy contains the above copyright
notice and this license. These can be included either as stand-alone text files,
human-readable headers or in the appropriate machine-readable metadata fields within
text or binary files as long as those fields can be easily viewed by the user.
.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless
explicit written permission is granted by the corresponding Copyright Holder. This
restriction only applies to the primary font name as presented to the users.
.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall
not be used to promote, endorse or advertise any Modified Version, except to
acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with
their explicit written permission.
.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed
entirely under this license, and must not be distributed under any other license. The
requirement for fonts to remain under this license does not apply to any document
created using the Font Software.
.
TERMINATION
.
This license becomes null and void if any of the above conditions are not met.
.
DISCLAIMER
.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER
RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -1,4 +1,5 @@
from __future__ import absolute_import
from __future__ import print_function
from django.core.management.base import BaseCommand

View File

@@ -1,4 +1,5 @@
from __future__ import absolute_import
from __future__ import print_function
import datetime
import pytz
@@ -19,6 +20,6 @@ class Command(BaseCommand):
date = datetime.datetime.now() - datetime.timedelta(days=1)
else:
date = datetime.datetime.strptime(options["date"], "%Y-%m-%d")
print "Activity data for", date
print activity_averages_during_day(date)
print "Please note that the total registered user count is a total for today"
print("Activity data for", date)
print(activity_averages_during_day(date))
print("Please note that the total registered user count is a total for today")

View File

@@ -1,4 +1,5 @@
from __future__ import absolute_import
from __future__ import print_function
from optparse import make_option
from django.core.management.base import BaseCommand
@@ -63,7 +64,7 @@ def compute_stats(log_level):
logging.info("Top %6s | %s%%" % (size, round(top_percents[size], 1)))
grand_total = sum(total_counts.values())
print grand_total
print(grand_total)
logging.info("%15s | %s" % ("Client", "Percentage"))
for client in total_counts.keys():
logging.info("%15s | %s%%" % (client, round(100. * total_counts[client] / grand_total, 1)))

View File

@@ -1,4 +1,5 @@
from __future__ import absolute_import
from __future__ import print_function
from zerver.lib.statistics import seconds_usage_between
@@ -16,7 +17,7 @@ def analyze_activity(options):
if options["realm"]:
user_profile_query = user_profile_query.filter(realm__domain=options["realm"])
print "Per-user online duration:\n"
print("Per-user online duration:\n")
total_duration = datetime.timedelta(0)
for user_profile in user_profile_query:
duration = seconds_usage_between(user_profile, day_start, day_end)
@@ -25,11 +26,11 @@ def analyze_activity(options):
continue
total_duration += duration
print "%-*s%s" % (37, user_profile.email, duration, )
print("%-*s%s" % (37, user_profile.email, duration, ))
print "\nTotal Duration: %s" % (total_duration,)
print "\nTotal Duration in minutes: %s" % (total_duration.total_seconds() / 60.,)
print "Total Duration amortized to a month: %s" % (total_duration.total_seconds() * 30. / 60.,)
print("\nTotal Duration: %s" % (total_duration,))
print("\nTotal Duration in minutes: %s" % (total_duration.total_seconds() / 60.,))
print("Total Duration amortized to a month: %s" % (total_duration.total_seconds() * 30. / 60.,))
class Command(BaseCommand):
help = """Report analytics of user activity on a per-user and realm basis.
@@ -42,7 +43,7 @@ It will correctly not count server-initiated reloads in the activity statistics.
The duration flag can be used to control how many days to show usage duration for
Usage: python manage.py analyze_user_activity [--realm=zulip.com] [--date=2013-09-10] [--duration=1]
Usage: python2.7 manage.py analyze_user_activity [--realm=zulip.com] [--date=2013-09-10] [--duration=1]
By default, if no date is selected 2013-09-10 is used. If no realm is provided, information
is shown for all realms"""

View File

@@ -1,4 +1,5 @@
from __future__ import absolute_import
from __future__ import print_function
from django.core.management.base import BaseCommand
from django.db.models import Count
@@ -13,9 +14,9 @@ class Command(BaseCommand):
Usage examples:
python manage.py client_activity
python manage.py client_activity zulip.com
python manage.py client_activity jesstess@zulip.com"""
python2.7 manage.py client_activity
python2.7 manage.py client_activity zulip.com
python2.7 manage.py client_activity jesstess@zulip.com"""
def add_arguments(self, parser):
parser.add_argument('arg', metavar='<arg>', type=str, nargs='?', default=None,
@@ -48,8 +49,8 @@ python manage.py client_activity jesstess@zulip.com"""
counts.sort()
for count in counts:
print "%25s %15d" % (count[1], count[0])
print "Total:", total
print("%25s %15d" % (count[1], count[0]))
print("Total:", total)
def handle(self, *args, **options):
@@ -70,5 +71,5 @@ python manage.py client_activity jesstess@zulip.com"""
self.compute_activity(UserActivity.objects.filter(
user_profile__realm=realm))
except Realm.DoesNotExist:
print "Unknown user or domain %s" % (arg,)
print("Unknown user or domain %s" % (arg,))
exit(1)

View File

@@ -1,4 +1,5 @@
from __future__ import absolute_import
from __future__ import print_function
import datetime
import pytz
@@ -6,7 +7,7 @@ import pytz
from django.core.management.base import BaseCommand
from django.db.models import Count
from zerver.models import UserProfile, Realm, Stream, Message, Recipient, UserActivity, \
Subscription, UserMessage
Subscription, UserMessage, get_realm
MOBILE_CLIENT_LIST = ["Android", "ios"]
HUMAN_CLIENT_LIST = MOBILE_CLIENT_LIST + ["website"]
@@ -65,52 +66,51 @@ class Command(BaseCommand):
fraction = 0.0
else:
fraction = numerator / float(denominator)
print "%.2f%% of" % (fraction * 100,), text
print("%.2f%% of" % (fraction * 100,), text)
def handle(self, *args, **options):
if options['realms']:
try:
realms = [Realm.objects.get(domain=domain) for domain in options['realms']]
except Realm.DoesNotExist, e:
print e
realms = [get_realm(domain) for domain in options['realms']]
except Realm.DoesNotExist as e:
print(e)
exit(1)
else:
realms = Realm.objects.all()
for realm in realms:
print realm.domain
print(realm.domain)
user_profiles = UserProfile.objects.filter(realm=realm, is_active=True)
active_users = self.active_users(realm)
num_active = len(active_users)
print "%d active users (%d total)" % (num_active, len(user_profiles))
print("%d active users (%d total)" % (num_active, len(user_profiles)))
streams = Stream.objects.filter(realm=realm).extra(
tables=['zerver_subscription', 'zerver_recipient'],
where=['zerver_subscription.recipient_id = zerver_recipient.id',
'zerver_recipient.type = 2',
'zerver_recipient.type_id = zerver_stream.id',
'zerver_subscription.active = true']).annotate(count=Count("name"))
print "%d streams" % (streams.count(),)
print("%d streams" % (streams.count(),))
for days_ago in (1, 7, 30):
print "In last %d days, users sent:" % (days_ago,)
print("In last %d days, users sent:" % (days_ago,))
sender_quantities = [self.messages_sent_by(user, days_ago) for user in user_profiles]
for quantity in sorted(sender_quantities, reverse=True):
print quantity,
print ""
print(quantity, end=' ')
print("")
print "%d stream messages" % (self.stream_messages(realm, days_ago),)
print "%d one-on-one private messages" % (self.private_messages(realm, days_ago),)
print "%d messages sent via the API" % (self.api_messages(realm, days_ago),)
print "%d group private messages" % (self.group_private_messages(realm, days_ago),)
print("%d stream messages" % (self.stream_messages(realm, days_ago),))
print("%d one-on-one private messages" % (self.private_messages(realm, days_ago),))
print("%d messages sent via the API" % (self.api_messages(realm, days_ago),))
print("%d group private messages" % (self.group_private_messages(realm, days_ago),))
num_notifications_enabled = len(filter(lambda x: x.enable_desktop_notifications == True,
active_users))
num_notifications_enabled = len([x for x in active_users if x.enable_desktop_notifications == True])
self.report_percentage(num_notifications_enabled, num_active,
"active users have desktop notifications enabled")
num_enter_sends = len(filter(lambda x: x.enter_sends, active_users))
num_enter_sends = len([x for x in active_users if x.enter_sends])
self.report_percentage(num_enter_sends, num_active,
"active users have enter-sends")
@@ -124,8 +124,8 @@ class Command(BaseCommand):
starrers = UserMessage.objects.filter(user_profile__in=user_profiles,
flags=UserMessage.flags.starred).values(
"user_profile").annotate(count=Count("user_profile"))
print "%d users have starred %d messages" % (
len(starrers), sum([elt["count"] for elt in starrers]))
print("%d users have starred %d messages" % (
len(starrers), sum([elt["count"] for elt in starrers])))
active_user_subs = Subscription.objects.filter(
user_profile__in=user_profiles, active=True)
@@ -133,20 +133,20 @@ class Command(BaseCommand):
# Streams not in home view
non_home_view = active_user_subs.filter(in_home_view=False).values(
"user_profile").annotate(count=Count("user_profile"))
print "%d users have %d streams not in home view" % (
len(non_home_view), sum([elt["count"] for elt in non_home_view]))
print("%d users have %d streams not in home view" % (
len(non_home_view), sum([elt["count"] for elt in non_home_view])))
# Code block markup
markup_messages = human_messages.filter(
sender__realm=realm, content__contains="~~~").values(
"sender").annotate(count=Count("sender"))
print "%d users have used code block markup on %s messages" % (
len(markup_messages), sum([elt["count"] for elt in markup_messages]))
print("%d users have used code block markup on %s messages" % (
len(markup_messages), sum([elt["count"] for elt in markup_messages])))
# Notifications for stream messages
notifications = active_user_subs.filter(notifications=True).values(
"user_profile").annotate(count=Count("user_profile"))
print "%d users receive desktop notifications for %d streams" % (
len(notifications), sum([elt["count"] for elt in notifications]))
print("%d users receive desktop notifications for %d streams" % (
len(notifications), sum([elt["count"] for elt in notifications])))
print ""
print("")

View File

@@ -1,8 +1,9 @@
from __future__ import absolute_import
from __future__ import print_function
from django.core.management.base import BaseCommand
from django.db.models import Q
from zerver.models import Realm, Stream, Message, Subscription, Recipient
from zerver.models import Realm, Stream, Message, Subscription, Recipient, get_realm
class Command(BaseCommand):
help = "Generate statistics on the streams for a realm."
@@ -14,27 +15,27 @@ class Command(BaseCommand):
def handle(self, *args, **options):
if options['realms']:
try:
realms = [Realm.objects.get(domain=domain) for domain in options['realms']]
except Realm.DoesNotExist, e:
print e
realms = [get_realm(domain) for domain in options['realms']]
except Realm.DoesNotExist as e:
print(e)
exit(1)
else:
realms = Realm.objects.all()
for realm in realms:
print realm.domain
print "------------"
print "%25s %15s %10s" % ("stream", "subscribers", "messages")
print(realm.domain)
print("------------")
print("%25s %15s %10s" % ("stream", "subscribers", "messages"))
streams = Stream.objects.filter(realm=realm).exclude(Q(name__istartswith="tutorial-"))
invite_only_count = 0
for stream in streams:
if stream.invite_only:
invite_only_count += 1
continue
print "%25s" % (stream.name,),
print("%25s" % (stream.name,), end=' ')
recipient = Recipient.objects.filter(type=Recipient.STREAM, type_id=stream.id)
print "%10d" % (len(Subscription.objects.filter(recipient=recipient, active=True)),),
print("%10d" % (len(Subscription.objects.filter(recipient=recipient, active=True)),), end=' ')
num_messages = len(Message.objects.filter(recipient=recipient))
print "%12d" % (num_messages,)
print "%d invite-only streams" % (invite_only_count,)
print ""
print("%12d" % (num_messages,))
print("%d invite-only streams" % (invite_only_count,))
print("")

View File

@@ -1,10 +1,12 @@
from __future__ import absolute_import
from __future__ import print_function
import datetime
import pytz
from django.core.management.base import BaseCommand
from zerver.models import UserProfile, Realm, Stream, Message
from zerver.models import UserProfile, Realm, Stream, Message, get_realm
from six.moves import range
class Command(BaseCommand):
help = "Generate statistics on user activity."
@@ -21,21 +23,21 @@ class Command(BaseCommand):
def handle(self, *args, **options):
if options['realms']:
try:
realms = [Realm.objects.get(domain=domain) for domain in options['realms']]
except Realm.DoesNotExist, e:
print e
realms = [get_realm(domain) for domain in options['realms']]
except Realm.DoesNotExist as e:
print(e)
exit(1)
else:
realms = Realm.objects.all()
for realm in realms:
print realm.domain
print(realm.domain)
user_profiles = UserProfile.objects.filter(realm=realm, is_active=True)
print "%d users" % (len(user_profiles),)
print "%d streams" % (len(Stream.objects.filter(realm=realm)),)
print("%d users" % (len(user_profiles),))
print("%d streams" % (len(Stream.objects.filter(realm=realm)),))
for user_profile in user_profiles:
print "%35s" % (user_profile.email,),
print("%35s" % (user_profile.email,), end=' ')
for week in range(10):
print "%5d" % (self.messages_sent_by(user_profile, week)),
print ""
print("%5d" % (self.messages_sent_by(user_profile, week)), end=' ')
print("")

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
from django.db import connection
from django.template import RequestContext, loader
from django.utils.html import mark_safe
@@ -15,6 +16,10 @@ import itertools
import time
import re
import pytz
from six.moves import filter
from six.moves import map
from six.moves import range
from six.moves import zip
eastern_tz = pytz.timezone('US/Eastern')
def make_table(title, cols, rows, has_row_class=False):
@@ -22,7 +27,7 @@ def make_table(title, cols, rows, has_row_class=False):
if not has_row_class:
def fix_row(row):
return dict(cells=row, row_class=None)
rows = map(fix_row, rows)
rows = list(map(fix_row, rows))
data = dict(title=title, cols=cols, rows=rows)
@@ -37,7 +42,7 @@ def dictfetchall(cursor):
"Returns all rows from a cursor as a dict"
desc = cursor.description
return [
dict(zip([col[0] for col in desc], row))
dict(list(zip([col[0] for col in desc], row)))
for row in cursor.fetchall()
]
@@ -226,7 +231,7 @@ def realm_summary_table(realm_minutes):
def meets_goal(row):
return row['active_user_count'] >= 5
num_active_sites = len(filter(meets_goal, rows))
num_active_sites = len(list(filter(meets_goal, rows)))
# create totals
total_active_user_count = 0
@@ -375,7 +380,7 @@ def ad_hoc_queries():
cursor = connection.cursor()
cursor.execute(query)
rows = cursor.fetchall()
rows = map(list, rows)
rows = list(map(list, rows))
cursor.close()
def fix_rows(i, fixup_func):
@@ -609,7 +614,7 @@ def raw_user_activity_table(records):
format_date_for_activity_reports(record.last_visit)
]
rows = map(row, records)
rows = list(map(row, records))
title = 'Raw Data'
return make_table(title, cols, rows)
@@ -816,7 +821,7 @@ def get_realm_activity(request, realm):
all_user_records = {}
try:
admins = Realm.objects.get(domain=realm).get_admin_users()
admins = get_realm(realm).get_admin_users()
except Realm.DoesNotExist:
return HttpResponseNotFound("Realm %s does not exist" % (realm,))

View File

@@ -31,11 +31,24 @@ file is as follows:
key=<api key from the web interface>
email=<your email address>
site=<your Zulip server's URI>
insecure=<true or false, true means do not verify the server certificate>
cert_bundle=<path to a file containing CA or server certificates to trust>
If omitted, these settings have the following defaults:
site=https://api.zulip.com
insecure=false
cert_bundle=<the default CA bundle trusted by Python>
Alternatively, you may explicitly use "--user" and "--api-key" in our
examples, which is especially useful if you are running several bots
which share a home directory. There is also a "--site" option for
setting the Zulip server on the command line.
which share a home directory.
The command line equivalents for other configuration options are:
--site=<your Zulip server's URI>
--insecure
--cert-bundle=<file>
You can obtain your Zulip API key, create bots, and manage bots all
from your Zulip [settings page](https://zulip.com/#settings).
@@ -101,3 +114,46 @@ Alternatively, if you don't want to use your ~/.zuliprc file:
--api-key a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5 \
hamlet@example.com cordelia@example.com -m \
"Conscience doth make cowards of us all."
#### Working with an untrusted server certificate
If your server has either a self-signed certificate, or a certificate signed
by a CA that you don't wish to globally trust then by default the API will
fail with an SSL verification error.
You can add `insecure=true` to your .zuliprc file.
[api]
site=https://zulip.example.com
insecure=true
This disables verification of the server certificate, so connections are
encrypted but unauthenticated. This is not secure, but may be good enough
for a development environment.
You can explicitly trust the server certificate using `cert_bundle=<filename>`
in your .zuliprc file.
[api]
site=https://zulip.example.com
cert_bundle=/home/bots/certs/zulip.example.com.crt
You can also explicitly trust a different set of Certificate Authorities from
the default bundle that is trusted by Python. For example to trust a company
internal CA.
[api]
site=https://zulip.example.com
cert_bundle=/home/bots/certs/example.com.ca-bundle
Save the server certificate (or the CA certificate) in its own file,
converting to PEM format first if necessary.
Verify that the certificate you have saved is the same as the one on the
server.
The `cert_bundle` option trusts the server / CA certificate only for
interaction with the zulip site, and is relatively secure.
Note that a certificate bundle is merely one or more certificates combined
into a single file.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# zulip-send -- Sends a message to the specified recipients.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012-2014 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2014 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
# Copyright © 2012 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Copyright © 2014 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Asana integration for Zulip

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Copyright © 2014 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Zulip mirror of Basecamp activity
@@ -49,7 +49,7 @@ client = zulip.Client(
site=config.ZULIP_SITE,
api_key=config.ZULIP_API_KEY,
client="ZulipBasecamp/" + VERSION)
user_agent = "Basecamp To Zulip Mirroring script (support@zulip.com)"
user_agent = "Basecamp To Zulip Mirroring script (zulip-devel@googlegroups.com)"
htmlParser = HTMLParser()
# find some form of JSON loader/dumper, with a preference order for speed.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Copyright © 2014 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Zulip mirror of Codebase HQ activity
@@ -58,7 +58,7 @@ client = zulip.Client(
site=config.ZULIP_SITE,
api_key=config.ZULIP_API_KEY,
client="ZulipCodebase/" + VERSION)
user_agent = "Codebase To Zulip Mirroring script (support@zulip.com)"
user_agent = "Codebase To Zulip Mirroring script (zulip-devel@googlegroups.com)"
# find some form of JSON loader/dumper, with a preference order for speed.
json_implementations = ['ujson', 'cjson', 'simplejson', 'json']

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Zulip notification post-receive hook.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Copyright © 2014 Zulip, Inc.

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Zulip hook for Mercurial changeset pushes.
# Copyright © 2012-2014 Zulip, Inc.
# Copyright © 2012-2014 Zulip, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -25,8 +25,10 @@
#
# This hook is called when changesets are pushed to the master repository (ie
# `hg push`). See https://zulip.com/integrations for installation instructions.
from __future__ import absolute_import
import zulip
from six.moves import range
VERSION = "0.9"

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
import optparse
import zulip

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Copyright © 2012-2014 Zulip, Inc.
@@ -33,6 +33,7 @@ For example:
1234 //depot/security/src/
'''
from __future__ import print_function
import os
import sys
@@ -59,12 +60,12 @@ try:
changelist = int(sys.argv[1])
changeroot = sys.argv[2]
except IndexError:
print >> sys.stderr, "Wrong number of arguments.\n\n",
print >> sys.stderr, __doc__
print("Wrong number of arguments.\n\n", end=' ', file=sys.stderr)
print(__doc__, file=sys.stderr)
sys.exit(-1)
except ValueError:
print >> sys.stderr, "First argument must be an integer.\n\n",
print >> sys.stderr, __doc__
print("First argument must be an integer.\n\n", end=' ', file=sys.stderr)
print(__doc__, file=sys.stderr)
sys.exit(-1)
metadata = git_p4.p4_describe(changelist)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Copyright © 2014 Zulip, Inc.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# RSS integration for Zulip

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Zulip notification post-commit hook.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Copyright © 2014 Zulip, Inc.

View File

@@ -107,8 +107,8 @@ class ZulipPlugin(Component):
field_changes = []
for key in old_values.keys():
if key == "description":
content += '- Changed %s from %s to %s' % (key, markdown_block(old_values.get(key)),
markdown_block(ticket.values.get(key)))
content += '- Changed %s from %s\n\nto %s' % (key, markdown_block(old_values.get(key)),
markdown_block(ticket.values.get(key)))
elif old_values.get(key) == "":
field_changes.append('%s: => **%s**' % (key, ticket.values.get(key)))
elif ticket.values.get(key) == "":

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Twitter integration for Zulip

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Twitter search integration for Zulip

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
from __future__ import print_function
import os
import sys
@@ -9,8 +10,8 @@ import itertools
def version():
version_py = os.path.join(os.path.dirname(__file__), "zulip", "__init__.py")
with open(version_py) as in_handle:
version_line = itertools.dropwhile(lambda x: not x.startswith("__version__"),
in_handle).next()
version_line = next(itertools.dropwhile(lambda x: not x.startswith("__version__"),
in_handle))
version = version_line.split('=')[-1].strip().replace('"', '')
return version
@@ -26,7 +27,7 @@ package_info = dict(
version=version(),
description='Bindings for the Zulip message API',
author='Zulip, Inc.',
author_email='support@zulip.com',
author_email='zulip-devel@googlegroups.com',
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Web Environment',
@@ -60,13 +61,13 @@ except ImportError:
try:
import simplejson
except ImportError:
print >>sys.stderr, "simplejson is not installed"
print("simplejson is not installed", file=sys.stderr)
sys.exit(1)
try:
import requests
assert(LooseVersion(requests.__version__) >= LooseVersion('0.12.1'))
except (ImportError, AssertionError):
print >>sys.stderr, "requests >=0.12.1 is not installed"
print("requests >=0.12.1 is not installed", file=sys.stderr)
sys.exit(1)

View File

@@ -20,6 +20,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from __future__ import print_function
from __future__ import absolute_import
import simplejson
import requests
import time
@@ -33,8 +35,9 @@ import urllib
import random
from distutils.version import LooseVersion
from ConfigParser import SafeConfigParser
from six.moves.configparser import SafeConfigParser
import logging
import six
__version__ = "0.2.4"
@@ -87,7 +90,7 @@ class RandomExponentialBackoff(CountingBackoff):
try:
logger.warning(message)
except NameError:
print message
print(message)
time.sleep(delay)
def _default_client():
@@ -117,6 +120,19 @@ def generate_option_group(parser, prefix=''):
default=None,
dest="zulip_client",
help=optparse.SUPPRESS_HELP)
group.add_option('--insecure',
action='store_true',
dest='insecure',
help='''Do not verify the server certificate.
The https connection will not be secure.''')
group.add_option('--cert-bundle',
action='store',
dest='cert_bundle',
help='''Specify a file containing either the
server certificate, or a set of trusted
CA certificates. This will be used to
verify the server's identity. All
certificates should be PEM encoded.''')
return group
def init_from_options(options, client=None):
@@ -126,7 +142,8 @@ def init_from_options(options, client=None):
client = _default_client()
return Client(email=options.zulip_email, api_key=options.zulip_api_key,
config_file=options.zulip_config_file, verbose=options.verbose,
site=options.zulip_site, client=client)
site=options.zulip_site, client=client,
cert_bundle=options.cert_bundle, insecure=options.insecure)
def get_default_config_filename():
config_file = os.path.join(os.environ["HOME"], ".zuliprc")
@@ -138,15 +155,14 @@ def get_default_config_filename():
class Client(object):
def __init__(self, email=None, api_key=None, config_file=None,
verbose=False, retry_on_errors=True,
site=None, client=None):
site=None, client=None,
cert_bundle=None, insecure=None):
if client is None:
client = _default_client()
if None in (api_key, email):
if config_file is None:
config_file = get_default_config_filename()
if not os.path.exists(config_file):
raise RuntimeError("api_key or email not specified and %s does not exist"
% (config_file,))
if config_file is None:
config_file = get_default_config_filename()
if os.path.exists(config_file):
config = SafeConfigParser()
with file(config_file, 'r') as f:
config.readfp(f, config_file)
@@ -156,6 +172,22 @@ class Client(object):
email = config.get("api", "email")
if site is None and config.has_option("api", "site"):
site = config.get("api", "site")
if cert_bundle is None and config.has_option("api", "cert_bundle"):
cert_bundle = config.get("api", "cert_bundle")
if insecure is None and config.has_option("api", "insecure"):
# Be quite strict about what is accepted so that users don't
# disable security unintentionally.
insecure_setting = config.get("api", "insecure").lower()
if insecure_setting == "true":
insecure = True
elif insecure_setting == "false":
insecure = False
else:
raise RuntimeError("insecure is set to '%s', it must be 'true' or 'false' if it is used in %s"
% (insecure_setting, config_file))
elif None in (api_key, email):
raise RuntimeError("api_key or email not specified and %s does not exist"
% (config_file,))
self.api_key = api_key
self.email = email
@@ -175,6 +207,17 @@ class Client(object):
self.retry_on_errors = retry_on_errors
self.client_name = client
if insecure:
self.tls_verification=False
elif cert_bundle is not None:
if not os.path.isfile(cert_bundle):
raise RuntimeError("tls bundle '%s' does not exist"
%(cert_bundle,))
self.tls_verification=cert_bundle
else:
# Default behavior: verify against system CA certificates
self.tls_verification=True
def get_user_agent(self):
vendor = ''
vendor_version = ''
@@ -203,7 +246,7 @@ class Client(object):
request = {}
for (key, val) in orig_request.iteritems():
if not (isinstance(val, str) or isinstance(val, unicode)):
if not (isinstance(val, str) or isinstance(val, six.text_type)):
request[key] = simplejson.dumps(val)
else:
request[key] = val
@@ -233,9 +276,9 @@ class Client(object):
def end_error_retry(succeeded):
if query_state["had_error_retry"] and self.verbose:
if succeeded:
print "Success!"
print("Success!")
else:
print "Failed!"
print("Failed!")
while True:
try:
@@ -249,7 +292,7 @@ class Client(object):
urlparse.urljoin(self.base_url, url),
auth=requests.auth.HTTPBasicAuth(self.email,
self.api_key),
verify=True, timeout=90,
verify=self.tls_verification, timeout=90,
headers={"User-agent": self.get_user_agent()},
**kwargs)
@@ -311,7 +354,7 @@ class Client(object):
else:
req_url = url
return self.do_api_query(request, API_VERSTRING + req_url, method=method, **query_kwargs)
call.func_name = name
call.__name__ = name
setattr(cls, name, call)
def call_on_each_event(self, callback, event_types=None, narrow=[]):
@@ -324,7 +367,7 @@ class Client(object):
if 'error' in res.get('result'):
if self.verbose:
print "Server returned error:\n%s" % res['msg']
print("Server returned error:\n%s" % res['msg'])
time.sleep(1)
else:
return (res['queue_id'], res['last_event_id'])
@@ -338,13 +381,13 @@ class Client(object):
if 'error' in res.get('result'):
if res["result"] == "http-error":
if self.verbose:
print "HTTP error fetching events -- probably a server restart"
print("HTTP error fetching events -- probably a server restart")
elif res["result"] == "connection-error":
if self.verbose:
print "Connection error fetching events -- probably server is temporarily down?"
print("Connection error fetching events -- probably server is temporarily down?")
else:
if self.verbose:
print "Server returned error:\n%s" % res["msg"]
print("Server returned error:\n%s" % res["msg"])
if res["msg"].startswith("Bad event queue id:"):
# Our event queue went away, probably because
# we were asleep or the server restarted

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
import xml.etree.ElementTree as ET
import subprocess

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
from __future__ import absolute_import
import os

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
import sys
import logging
import os

View File

@@ -11,5 +11,5 @@ ZULIP_DIR=/home/zulip/deployments/current
STATE_DIR=/var/lib/nagios_state
STATE_FILE=$STATE_DIR/check-rabbitmq-consumers-$queue
$ZULIP_DIR/bots/check-rabbitmq-consumers --queue=$queue &> ${STATE_FILE}-tmp;
mv ${STATE_FILE}-tmp $STATE_FILE
"$ZULIP_DIR/bots/check-rabbitmq-consumers" "--queue=$queue" &> "${STATE_FILE}-tmp";
mv "${STATE_FILE}-tmp" "$STATE_FILE"

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
import sys
import time
import optparse

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
import sys
import time

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
import sys
import re

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
import time
def nagios_from_file(results_file):

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
import sys
import time
import datetime

View File

@@ -1,4 +1,4 @@
#! /usr/bin/env python
#! /usr/bin/env python2.7
#
# EXPERIMENTAL
# IRC <=> Zulip mirroring bot
@@ -6,6 +6,7 @@
# Setup: First, you need to install python-irc version 8.5.3
# (https://bitbucket.org/jaraco/irc)
from __future__ import print_function
import irc.bot
import irc.strings
from irc.client import ip_numstr_to_quad, ip_quad_to_numstr
@@ -53,12 +54,12 @@ class IRCBot(irc.bot.SingleServerIRCBot):
return
# Forward the PM to Zulip
print zulip_client.send_message({
print(zulip_client.send_message({
"sender": sender,
"type": "private",
"to": "username@example.com",
"content": content,
})
}))
def on_pubmsg(self, c, e):
content = e.arguments[0]
@@ -68,14 +69,14 @@ class IRCBot(irc.bot.SingleServerIRCBot):
return
# Forward the stream message to Zulip
print zulip_client.send_message({
print(zulip_client.send_message({
"forged": "yes",
"sender": sender,
"type": "stream",
"to": stream,
"subject": "IRC",
"content": content,
})
}))
def on_dccmsg(self, c, e):
c.privmsg("You said: " + e.arguments[0])
@@ -92,11 +93,11 @@ class IRCBot(irc.bot.SingleServerIRCBot):
return
self.dcc_connect(address, port)
usage = """python irc-mirror.py --server=IRC_SERVER --channel=<CHANNEL> --nick-prefix=<NICK> [optional args]
usage = """python2.7 irc-mirror.py --server=IRC_SERVER --channel=<CHANNEL> --nick-prefix=<NICK> [optional args]
Example:
python irc-mirror.py --irc-server=127.0.0.1 --channel='#test' --nick-prefix=username
python2.7 irc-mirror.py --irc-server=127.0.0.1 --channel='#test' --nick-prefix=username
--site=https://zulip.example.com --user=irc-bot@example.com
--api-key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# Copyright (C) 2014 Zulip, Inc.
#
# Permission is hereby granted, free of charge, to any person
@@ -21,6 +21,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import print_function
import sys
import subprocess
import os
@@ -39,7 +40,7 @@ args.extend(sys.argv[1:])
backoff = RandomExponentialBackoff(timeout_success_equivalent=300)
while backoff.keep_going():
print "Starting Jabber mirroring bot"
print("Starting Jabber mirroring bot")
try:
ret = subprocess.call(args)
except:
@@ -51,9 +52,9 @@ while backoff.keep_going():
backoff.fail()
print ""
print ""
print "ERROR: The Jabber mirroring bot is unable to continue mirroring Jabber."
print "Please contact support@zulip.com if you need assistence."
print ""
print("")
print("")
print("ERROR: The Jabber mirroring bot is unable to continue mirroring Jabber.")
print("Please contact zulip-devel@googlegroups.com if you need assistance.")
print("")
sys.exit(1)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python2.7
#
# Copyright (C) 2013 Permabit, Inc.
# Copyright (C) 2013--2014 Zulip, Inc.
@@ -44,7 +44,7 @@ import optparse
from sleekxmpp import ClientXMPP, InvalidJID, JID
from sleekxmpp.exceptions import IqError, IqTimeout
from ConfigParser import SafeConfigParser
from six.moves.configparser import SafeConfigParser
import os, sys, zulip, getpass
import re

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python2.7
import subprocess
import os
import sys

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python2.7
import sys
import subprocess
import base64

View File

@@ -1,3 +1,4 @@
from __future__ import print_function
# This is hacky code to analyze data on our support stream. The main
# reusable bits are get_recent_messages and get_words.
@@ -31,7 +32,7 @@ def analyze_messages(msgs, word_count, email_count):
if False:
if ' ack' in msg['content']:
name = msg['sender_full_name'].split()[0]
print 'ACK', name
print('ACK', name)
m = re.search('ticket (Z....).*email: (\S+).*~~~(.*)', msg['content'], re.M | re.S)
if m:
ticket, email, req = m.groups()
@@ -40,9 +41,9 @@ def analyze_messages(msgs, word_count, email_count):
word_count[word] += 1
email_count[email] += 1
if False:
print
print()
for k, v in msg.items():
print '%-20s: %s' % (k, v)
print('%-20s: %s' % (k, v))
def generate_support_stats():
client = zulip.Client()
@@ -64,16 +65,16 @@ def generate_support_stats():
if True:
words = word_count.keys()
words = filter(lambda w: word_count[w] >= 10, words)
words = filter(lambda w: len(w) >= 5, words)
words = [w for w in words if word_count[w] >= 10]
words = [w for w in words if len(w) >= 5]
words = sorted(words, key=lambda w: word_count[w], reverse=True)
for word in words:
print word, word_count[word]
print(word, word_count[word])
if False:
emails = email_count.keys()
emails = sorted(emails, key=lambda w: email_count[w], reverse=True)
for email in emails:
print email, email_count[email]
print(email, email_count[email])
generate_support_stats()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
import sys
import os
import logging

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# Copyright (C) 2012 Zulip, Inc.
#
# Permission is hereby granted, free of charge, to any person

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# Copyright (C) 2012 Zulip, Inc.
#
# Permission is hereby granted, free of charge, to any person
@@ -21,13 +21,15 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import absolute_import
from __future__ import print_function
import sys
import subprocess
import os
import traceback
import signal
from zephyr_mirror_backend import parse_args
from .zephyr_mirror_backend import parse_args
def die(signal, frame):
# We actually want to exit, so run os._exit (so as not to be caught and restarted)
@@ -52,30 +54,30 @@ if options.forward_class_messages and not options.noshard:
if options.on_startup_command is not None:
subprocess.call([options.on_startup_command])
from zerver.lib.parallel import run_parallel
print "Starting parallel zephyr class mirroring bot"
print("Starting parallel zephyr class mirroring bot")
jobs = list("0123456789abcdef")
def run_job(shard):
subprocess.call(args + ["--shard=%s" % (shard,)])
return 0
for (status, job) in run_parallel(run_job, jobs, threads=16):
print "A mirroring shard died!"
print("A mirroring shard died!")
pass
sys.exit(0)
backoff = RandomExponentialBackoff(timeout_success_equivalent=300)
while backoff.keep_going():
print "Starting zephyr mirroring bot"
print("Starting zephyr mirroring bot")
try:
subprocess.call(args)
except:
traceback.print_exc()
backoff.fail()
print ""
print ""
print "ERROR: The Zephyr mirroring bot is unable to continue mirroring Zephyrs."
print "This is often caused by failing to maintain unexpired Kerberos tickets"
print "or AFS tokens. See https://zulip.com/zephyr for documentation on how to"
print "maintain unexpired Kerberos tickets and AFS tokens."
print ""
print("")
print("")
print("ERROR: The Zephyr mirroring bot is unable to continue mirroring Zephyrs.")
print("This is often caused by failing to maintain unexpired Kerberos tickets")
print("or AFS tokens. See https://zulip.com/zephyr for documentation on how to")
print("maintain unexpired Kerberos tickets and AFS tokens.")
print("")
sys.exit(1)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# Copyright (C) 2012 Zulip, Inc.
#
# Permission is hereby granted, free of charge, to any person
@@ -20,8 +20,11 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import absolute_import
import sys
from six.moves import map
from six.moves import range
try:
import simplejson
except ImportError:
@@ -41,8 +44,8 @@ import select
DEFAULT_SITE = "https://api.zulip.com"
class States:
Startup, ZulipToZephyr, ZephyrToZulip, ChildSending = range(4)
class States(object):
Startup, ZulipToZephyr, ZephyrToZulip, ChildSending = list(range(4))
CURRENT_STATE = States.Startup
def to_zulip_username(zephyr_username):

31
changelog.md Normal file
View File

@@ -0,0 +1,31 @@
# Change Log
All notable changes to this project will be documented in this file.
[Unreleased]
[1.3.9] - 2015-11-16
- Fixed buggy #! lines in upgrade scripts.
[1.3.8] - 2015-11-15
- Added options to the Python api for working with untrusted server certificates.
- Added a lot of documentation on the development environment and testing.
- Added partial support for translating the Zulip UI.
- Migrated installing Node dependencies to use npm.
- Fixed LDAP integration breaking autocomplete of @-mentions.
- Fixed admin panel reactivation/deactivation of bots.
- Fixed inaccurate documentation for downloading the desktop apps.
- Fixed various minor bugs in production installation process.
- Fixed security issue where recent history on private streams might
be visible to new users (to the Zulip team) who were invited with that
private stream as one of their initial streams
(https://github.com/zulip/zulip/issues/230).
- Major preliminary progress towards supporting Python 3.
[1.3.7] - 2015-10-19
- Turn off desktop and audible notifications for streams by default.
- Added support for the LDAP authentication integration creating new users.
- Added new endpoint to support Google auth on mobile.
- Fixed desktop notifications in modern Firefox.
- Fixed several installation issues for both production and development environments.
- Improved documentation for outgoing SMTP and the email mirror integration.

View File

@@ -105,7 +105,7 @@ class Confirmation(models.Model):
objects = ConfirmationManager()
class Meta:
class Meta(object):
verbose_name = _('confirmation email')
verbose_name_plural = _('confirmation emails')

View File

@@ -352,8 +352,8 @@ styles (separate lines for each selector)::
Python
------
- Scripts should start with ``#!/usr/bin/env python`` and not
``#!/usr/bin/python``. See commit ``437d4aee`` for an explanation of
- Scripts should start with ``#!/usr/bin/env python2.7`` and not
``#!/usr/bin/env python2.7``. See commit ``437d4aee`` for an explanation of
why. Don't put such a line on a Python file unless it's meaningful to
run it as a script. (Some libraries can also be run as scripts, e.g.
to run a test suite.)

View File

@@ -76,11 +76,11 @@ Tests
=====
+------------------------+-----------------------------------+
| ``zerver/test*.py`` | Backend tests | |
| ``zerver/test*.py`` | Backend tests |
+------------------------+-----------------------------------+
| ``zerver/tests/frontend/node`` | Node Frontend unit tests |
| ``frontend_tests/node`` | Node Frontend unit tests |
+------------------------+-----------------------------------+
| ``zerver/tests/frontend/tests`` | Casper frontend tests |
| ``frontend_tests/tests`` | Casper frontend tests |
+------------------------+-----------------------------------+
Documentation

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python2.7
# Remove HTML entity escaping left over from MediaWiki->rST conversion.

View File

@@ -2,84 +2,215 @@
New Feature Tutorial
====================
.. attention::
This tutorial is an unfinished work -- contributions welcome!
The changes needed to add a new feature will vary, of course, but this document
provides a general outline of what you may need to do, as well as an example of
the specific steps needed to add a new feature: adding a new option to the
application that is dynamically synced through the data system in real-time to
all browsers the user may have open.
The changes needed to add a new feature will vary, of course. We give an
example here that illustrates some of the common steps needed. We describe
the process of adding a new setting for admins that restricts inviting new
users to admins only.
Backend Changes
General Process
===============
Adding a field to the database
------------------------------
The server accesses the underlying database in `zerver/models.py`. Add
a new field in the appropriate class, `realm_invite_by_admins_only`
in the `Realm` class in this case.
**Update the model:** The server accesses the underlying database in `zerver/
models.py`. Add a new field in the appropriate class.
Once you do so, you need to create the migration and run it; the
process is documented at:
https://docs.djangoproject.com/en/1.8/topics/migrations/
**Create and run the migration:** To create and apply a migration, run: ::
Once you've run the migration, to test your changes, you'll want to
restart memcached on your development server (``/etc/init.d/memcached restart``) and
then restart ``run-dev.py`` to avoid interacting with cached objects.
./manage.py makemigrations
./manage.py migrate
**Test your changes:** Once you've run the migration, restart memcached on your
development server (``/etc/init.d/memcached restart``) and then restart
``run-dev.py`` to avoid interacting with cached objects.
Backend changes
---------------
You should add code in `zerver/lib/actions.py` to interact with the database,
that actually updates the relevant field. In this case, `do_set_realm_invite_by_admins_only`
is a function that actually updates the field in the database, and sends
an event announcing that this change has been made.
**Database interaction:** Add any necessary code for updating and interacting
with the database in ``zerver/lib/actions.py``. It should update the database and
send an event announcing the change.
You then need update the `fetch_initial_state_data` and `apply_events` functions
in `zerver/lib/actions.py` to update the state based on the event you just created.
In this case, we add a line
**Application state:** Modify the ``fetch_initial_state_data`` and ``apply_events``
functions in ``zerver/lib/actions.py`` to update the state based on the event you
just created.
::
**Backend implementation:** Make any other modifications to the backend required for
your change.
state['realm_invite_by_admins_only'] = user_profile.realm.invite_by_admins_only`
to the `fetch_initial_state_data` function. The `apply_events` function
doesn't need to be updated since
::
elif event['type'] == 'realm':
field = 'realm_' + event['property']
state[field] = event['value']
already took care of our event.
Then update `zerver/views/__init__.py` to actually call your function.
In the dictionary which sets the javascript `page_params` dictionary,
add a value for your feature.
::
realm_invite_by_admins_only = register_ret['realm_invite_by_admins_only']
Perhaps your new option controls some other backend rendering: in our case
we test for this option in the `home` method for adding a variable to the response.
The functions in this file control the generation of various pages served
(along with the Django templates).
Our new feature also shows up in the administration tab (as a checkbox),
so we need to update the `update_realm` function.
Finally, add tests for your backend changes; at the very least you
should add a test of your event data flowing through the system in
``test_events.py``.
**Testing:** At the very least, add a test of your event data flowing through
the system in ``test_events.py``.
Frontend changes
----------------
You need to change various things on the front end. In this case, the relevant files
are `static/js/server_events.js`, `static/js/admin.js`, `static/styles/zulip.css
and `static/templates/admin_tab.handlebars`.
**JavaScript:** Zulip's JavaScript is located in the directory ``static/js/``.
The exact files you may need to change depend on your feature. If you've added a
new event that is sent to clients, be sure to add a handler for it to
``static/js/server_events.js``.
**CSS:** The primary CSS file is ``static/styles/zulip.css``. If your new
feature requires UI changes, you may need to add additional CSS to this file.
**Templates:** The initial page structure is rendered via Django templates
located in ``template/server``. For JavaScript, Zulip uses Handlebars templates located in
``static/templates``. Templates are precompiled as part of the build/deploy
process.
**Testing:** There are two types of frontend tests: node-based unit tests and
blackbox end-to-end tests. The blackbox tests are run in a headless browser
using Casper.js and are located in ``zerver/tests/frontend/tests/``. The unit
tests use Node's ``assert`` module are located in ``zerver/tests/frontend/node/``.
For more information on writing and running tests see the :doc:`testing
documentation <testing>`.
Example Feature
===============
This example describes the process of adding a new setting to Zulip:
a flag that restricts inviting new users to admins only (the default behavior
is that any user can invite other users). It is based on an actual Zulip feature,
and you can review `the original commit in the Zulip git repo <https://github.com/zulip/zulip/commit/5b7f3466baee565b8e5099bcbd3e1ccdbdb0a408>`_.
(Note that Zulip has since been upgraded from Django 1.6 to 1.8, so the migration
format has changed.)
First, update the database and model to store the new setting. Add a
new boolean field, ``realm_invite_by_admins_only``, to the Realm model in
``zerver/models.py``.
Then create a Django migration that adds a new field, ``invite_by_admins_only``,
to the ``zerver_realm`` table.
In ``zerver/lib/actions.py``, create a new function named
``do_set_realm_invite_by_admins_only``. This function will update the database
and trigger an event to notify clients when this setting changes. In this case
there was an exisiting ``realm|update`` event type which was used for setting
similar flags on the Realm model, so it was possible to add a new property to
that event rather than creating a new one. The property name matches the
database field to make it easy to understand what it indicates.
The second argument to ``send_event`` is the list of users whose browser
sessions should be notified. Depending on the setting, this can be a single user
(if the setting is a personal one, like time display format), only members in a
particular stream or all active users in a realm. ::
# zerver/lib/actions.py
def do_set_realm_invite_by_admins_only(realm, invite_by_admins_only):
realm.invite_by_admins_only = invite_by_admins_only
realm.save(update_fields=['invite_by_admins_only'])
event = dict(
type="realm",
op="update",
property='invite_by_admins_only',
value=invite_by_admins_only,
)
send_event(event, active_user_ids(realm))
return {}
You then need to add code that will handle the event and update the application
state. In ``zerver/lib/actions.py`` update the ``fetch_initial_state`` and
``apply_events`` functions. ::
def fetch_initial_state_data(user_profile, event_types, queue_id):
# ...
state['realm_invite_by_admins_only'] = user_profile.realm.invite_by_admins_only`
In this case you don't need to change ``apply_events`` because there is already
code that will correctly handle the realm update event type: ::
def apply_events(state, events, user_profile):
for event in events:
# ...
elif event['type'] == 'realm':
field = 'realm_' + event['property']
state[field] = event['value']
You then need to add a view for clients to access that will call the newly-added
``actions.py`` code to update the database. This example feature adds a new
parameter that should be sent to clients when the application loads and be
accessible via JavaScript, and there is already a view that does this for
related flags: ``update_realm``. So in this case, we can add out code to the
exisiting view instead of creating a new one. ::
# zerver/views/__init__.py
def home(request):
# ...
page_params = dict(
# ...
realm_invite_by_admins_only = register_ret['realm_invite_by_admins_only'],
# ...
)
Since this feature also adds a checkbox to the admin page, and adds a new
property the Realm model that can be modified from there, you also need to make
changes to the ``update_realm`` function in the same file: ::
# zerver/views/__init__.py
def update_realm(request, user_profile,
name=REQ(validator=check_string, default=None),
restricted_to_domain=REQ(validator=check_bool, default=None),
invite_by_admins_only=REQ(validator=check_bool,default=None)):
# ...
if invite_by_admins_only is not None and
realm.invite_by_admins_only != invite_by_admins_only:
do_set_realm_invite_by_admins_only(realm, invite_by_admins_only)
data['invite_by_admins_only'] = invite_by_admins_only
Then make the required front end changes: in this case a checkbox needs to be
added to the admin page (and its value added to the data sent back to server
when a realm is updated) and the change event needs to be handled on the client.
To add the checkbox to the admin page, modify the relevant template,
``static/templates/admin_tab.handlebars`` (omitted here since it is relatively
straightforward). Then add code to handle changes to the new form control in
``static/js/admin.js``. ::
var url = "/json/realm";
var new_invite_by_admins_only =
$("#id_realm_invite_by_admins_only").prop("checked");
data[invite_by_admins_only] = JSON.stringify(new_invite_by_admins_only);
channel.patch({
url: url,
data: data,
success: function (data) {
# ...
if (data.invite_by_admins_only) {
ui.report_success("New users must be invited by an admin!", invite_by_admins_only_status);
} else {
ui.report_success("Any user may now invite new users!", invite_by_admins_only_status);
}
# ...
}
});
Finally, update ``server_events.js`` to handle related events coming from the
server. ::
# static/js/server_events.js
function get_events_success(events) {
# ...
var dispatch_event = function dispatch_event(event) {
switch (event.type) {
# ...
case 'realm':
if (event.op === 'update' && event.property === 'invite_by_admins_only') {
page_params.realm_invite_by_admins_only = event.value;
}
}
}
Any code needed to update the UI should be placed in ``dispatch_event`` callback
(rather than the ``channel.patch``) function. This ensures the appropriate code
will run even if the changes are made in another browser window. In this example
most of the changes are on the backend, so no UI updates are required.

View File

@@ -56,10 +56,10 @@ Backend Django tests
These live in ``zerver/tests.py`` and ``zerver/test_*.py``. Run them
with ``tools/test-backend``.
Web frontend black-box tests
----------------------------
Web frontend black-box casperjs tests
-------------------------------------
These live in ``zerver/tests/frontend/tests/``. This is a "black box"
These live in ``frontend_tests/casper_tests/``. This is a "black box"
test; we load the frontend in a real (headless) browser, from a real dev
server, and simulate UI interactions like sending messages, narrowing,
etc.
@@ -67,8 +67,63 @@ etc.
Since this is interacting with a real dev server, it can catch backend
bugs as well.
You can run this with ``./zerver/tests/frontend/run``. You will need
`PhantomJS <http://phantomjs.org/>`__ 1.7.0 or later.
You can run this with ``./tools/test-js-with-casper`` or as
``./tools/test-js-with-casper 05-settings.js`` to run a single test
file from ``frontend_tests/casper_tests/``.
Writing Casper tests
~~~~~~~~~~~~~~~~~~~~
Probably the easiest way to learn how to write Casper tests is to
study some of the existing test files. There are a few tips that can
be useful for writing Casper tests in addition to the debugging notes
below:
- Run just the file containing your new tests as described above to
have a fast debugging cycle.
- With frontend tests in general, it's very important to write your
code to wait for the right events. Before essentially every action
you take on the page, you'll want to use ``waitForSelector``,
``waitUntilVisible``, or a similar function to make sure the page or
elemant is ready before you interact with it. For instance, if you
want to click a button that you can select via ``#btn-submit``, and
then check that it causes ``success-elt`` to appear, you'll want to
write something like:
::
casper.waitForSelector("#btn-submit", function () {
casper.click('#btn-submit')
casper.test.assertExists("#success-elt");
});
This will ensure that the element is present before the interaction
is attempted. The various wait functions supported in Casper are
documented in the Casper here:
http://docs.casperjs.org/en/latest/modules/casper.html#waitforselector
and the various assert statements available are documented here:
http://docs.casperjs.org/en/latest/modules/tester.html#the-tester-prototype
- Casper uses CSS3 selectors; you can often save time by testing and
debugigng your selectors on the relevant page of the Zulip
development app in the Chrome javascript console by using
e.g. ``$$("#settings-dropdown")``.
- The test suite uses a smaller set of default user accounts and other
data initialized in the database than the development environment;
to see what differs check out the section related to
``options["test_suite"]`` in
``zilencer/management/commands/populate_db.py``.
- Casper effectively runs your test file in two phases -- first it
runs the code in the test file, which for most test files will just
collect a series of steps (each being a ``casper.then`` or
``casper.wait...`` call). Then, usually at the end of the test
file, you'll have a ``casper.run`` call which actually runs that
series of steps. This means that if you If you write code in your
test file outside a ``casper.then`` or ``casper.wait...`` method, it
will actually run before all the Casper test steps that are declared
in the file, which can lead to confusing failures where the new code
you write in between two ``casper.then`` blocks actually runs before
either of them. See this for more details about how Casper works:
http://docs.casperjs.org/en/latest/faq.html#how-does-then-and-the-step-stack-work
Debugging Casper.JS
~~~~~~~~~~~~~~~~~~~
@@ -78,7 +133,7 @@ is not perfect. Here are some steps for using it and gotchas you might
want to know.
To turn on remote debugging, pass ``--remote-debug`` to the
``./zerver/frontend/tests/run`` script. This will run the tests with
``./frontend_tests/tests/run`` script. This will run the tests with
port ``7777`` open for remote debugging. You can now connect to
``localhost:7777`` in a Webkit browser. Somewhat recent versions of
Chrome or Safari might be required.
@@ -97,6 +152,23 @@ you set a breakpoint and it is hit, the inspector will pause and you can
do your normal JS debugging. You can also put breakpoints in the Zulip
webpage itself if you wish to inspect the state of the Zulip frontend.
If you need to use print debugging in casper, you can do using
``casper.log``; see http://docs.casperjs.org/en/latest/logging.html
for details.
An additional debugging technique is to enable verbose mode in the
Casper tests; you can do this by adding to the top of the relevant
test file the following:
::
var casper = require('casper').create({
verbose: true,
logLevel: "debug"
});
This can sometimes give insight into exactly what's happening.
Web frontend unit tests
-----------------------
@@ -114,7 +186,7 @@ bottom of ``foobar.js``:
This makes ``foobar.js`` follow the CommonJS module pattern, so it can
be required in Node.js, which runs our tests.
Now create ``zerver/tests/frontend/node/foobar.js``. At the top, require
Now create ``frontend_tests/node_tests/foobar.js``. At the top, require
the `Node.js assert module <http://nodejs.org/api/assert.html>`__, and
the module you're testing, like so:
@@ -140,12 +212,12 @@ asserts, the *actual* value comes first, the *expected* value second.
}());
The test runner (index.js) automatically runs all .js files in the
zerver/tests/frontend/node directory.
frontend_tests/node directory.
.. _handling-dependencies:
Handling dependencies in tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Handling dependencies in unit tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following scheme helps avoid tests leaking globals between each
other.

2
frontend_tests/casper_lib/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/server.log
/test_credentials.js

View File

@@ -2,7 +2,7 @@ var common = (function () {
var exports = {};
var test_credentials = require('../test_credentials.js').test_credentials;
var test_credentials = require('../casper_lib/test_credentials.js').test_credentials;
function timestamp() {
return new Date().getTime();

View File

@@ -1,4 +1,4 @@
var common = require('../common.js').common;
var common = require('../casper_lib/common.js').common;
// Start of test script.
casper.start('http://localhost:9981/', common.initialize_casper);

View File

@@ -10,7 +10,7 @@
// For example, utils.dump() prints an Object with nice formatting.
var utils = require('utils');
var common = require('../common.js').common;
var common = require('../casper_lib/common.js').common;
common.start_and_log_in();

View File

@@ -1,4 +1,4 @@
var common = require('../common.js').common;
var common = require('../casper_lib/common.js').common;
common.start_and_log_in();

View File

@@ -1,4 +1,4 @@
var common = require('../common.js').common;
var common = require('../casper_lib/common.js').common;
common.start_and_log_in();

View File

@@ -1,4 +1,4 @@
var common = require('../common.js').common;
var common = require('../casper_lib/common.js').common;
common.start_and_log_in();

View File

@@ -1,5 +1,5 @@
var common = require('../common.js').common;
var test_credentials = require('../test_credentials.js').test_credentials;
var common = require('../casper_lib/common.js').common;
var test_credentials = require('../casper_lib/test_credentials.js').test_credentials;
common.start_and_log_in();

View File

@@ -1,4 +1,4 @@
var common = require('../common.js').common;
var common = require('../casper_lib/common.js').common;
function star_count() {
return casper.evaluate(function () {

View File

@@ -1,4 +1,4 @@
var common = require('../common.js').common;
var common = require('../casper_lib/common.js').common;
common.start_and_log_in();

View File

@@ -1,4 +1,4 @@
var common = require('../common.js').common;
var common = require('../casper_lib/common.js').common;
// Test basic tab navigation.

View File

@@ -0,0 +1,77 @@
var common = require('../casper_lib/common.js').common;
var test_credentials = require('../casper_lib/test_credentials.js').test_credentials;
common.start_and_log_in();
casper.then(function () {
casper.test.info('Administration page');
casper.click('a[href^="#administration"]');
casper.test.assertUrlMatch(/^http:\/\/[^\/]+\/#administration/, 'URL suggests we are on administration page');
casper.test.assertExists('#administration.tab-pane.active', 'Administration page is active');
});
// Test user deactivation and reactivation
casper.waitForSelector('.user_row[id="user_cordelia@zulip.com"]', function () {
casper.test.assertSelectorHasText('.user_row[id="user_cordelia@zulip.com"]', 'Deactivate');
casper.click('.user_row[id="user_cordelia@zulip.com"] .deactivate');
casper.test.assertTextExists('Deactivate cordelia@zulip.com', 'Deactivate modal has right user');
casper.test.assertTextExists('Deactivate now', 'Deactivate now button available');
casper.click('#do_deactivate_user_button');
});
casper.waitForSelector('.user_row[id="user_cordelia@zulip.com"].deactivated_user', function () {
casper.test.assertSelectorHasText('.user_row[id="user_cordelia@zulip.com"]', 'Reactivate');
casper.click('.user_row[id="user_cordelia@zulip.com"] .reactivate');
});
casper.waitForSelector('.user_row[id="user_cordelia@zulip.com"]:not(.deactivated_user)', function () {
casper.test.assertSelectorHasText('.user_row[id="user_cordelia@zulip.com"]', 'Deactivate');
});
// Test Deactivated users section of admin page
casper.waitForSelector('.user_row[id="user_cordelia@zulip.com"]', function () {
casper.test.assertSelectorHasText('.user_row[id="user_cordelia@zulip.com"]', 'Deactivate');
casper.click('.user_row[id="user_cordelia@zulip.com"] .deactivate');
casper.test.assertTextExists('Deactivate cordelia@zulip.com', 'Deactivate modal has right user');
casper.test.assertTextExists('Deactivate now', 'Deactivate now button available');
casper.click('#do_deactivate_user_button');
});
casper.then(function () {
// Leave the page and return
casper.click('#settings-dropdown');
casper.click('a[href^="#subscriptions"]');
casper.click('#settings-dropdown');
casper.click('a[href^="#administration"]');
casper.waitForSelector('.user_row[id="user_cordelia@zulip.com"]', function () {
casper.test.assertSelectorHasText('#admin_deactivated_users_table .user_row[id="user_cordelia@zulip.com"]', 'Reactivate');
casper.click('#admin_deactivated_users_table .user_row[id="user_cordelia@zulip.com"] .reactivate');
});
casper.waitForSelector('#admin_deactivated_users_table .user_row[id="user_cordelia@zulip.com"]:not(.deactivated_user)', function () {
casper.test.assertSelectorHasText('#admin_deactivated_users_table .user_row[id="user_cordelia@zulip.com"]', 'Deactivate');
});
});
// Test bot deactivation and reactivation
casper.waitForSelector('.user_row[id="user_new-user-bot@zulip.com"]', function () {
casper.test.assertSelectorHasText('.user_row[id="user_new-user-bot@zulip.com"]', 'Deactivate');
casper.click('.user_row[id="user_new-user-bot@zulip.com"] .deactivate');
});
casper.waitForSelector('.user_row[id="user_new-user-bot@zulip.com"].deactivated_user', function () {
casper.test.assertSelectorHasText('.user_row[id="user_new-user-bot@zulip.com"]', 'Reactivate');
casper.click('.user_row[id="user_new-user-bot@zulip.com"] .reactivate');
});
casper.waitForSelector('.user_row[id="user_new-user-bot@zulip.com"]:not(.deactivated_user)', function () {
casper.test.assertSelectorHasText('.user_row[id="user_new-user-bot@zulip.com"]', 'Deactivate');
});
// TODO: Test stream deletion
common.then_log_out();
casper.run(function () {
casper.test.done();
});

View File

@@ -175,7 +175,7 @@ function patchRequire(require, requireDirs) {
function bootstrap(global) {
"use strict";
var phantomArgs = require('system').args;
var system = require('system');
/**
* Hooks in default phantomjs error handler to print a hint when a possible
@@ -223,7 +223,7 @@ function bootstrap(global) {
// casper root path
if (!phantom.casperPath) {
try {
phantom.casperPath = phantom.args.map(function _map(i) {
phantom.casperPath = system.args.map(function _map(i) {
var match = i.match(/^--casper-path=(.*)/);
if (match) {
return fs.absolute(match[1]);
@@ -289,7 +289,7 @@ function bootstrap(global) {
global.require = patchRequire(global.require, [phantom.casperPath, fs.workingDirectory]);
// casper cli args
phantom.casperArgs = global.require('cli').parse(phantom.args);
phantom.casperArgs = global.require('cli').parse(system.args);
// loaded status
phantom.casperLoaded = true;

Some files were not shown because too many files have changed in this diff Show More