2016-06-26 18:49:35 +02:00
# Writing a new application feature
2016-05-15 18:28:38 +02:00
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.
2017-02-10 10:09:31 +01:00
As you read this, you may find you need to learn about Zulip's
real-time push system; the
2019-09-30 19:37:56 +02:00
[real-time push and events ](../subsystems/events-system.md )
2017-11-12 22:14:15 +01:00
documentation has a detailed explanation of how everything works. You
may also find it beneficial to read Zulip's
2019-09-30 19:37:56 +02:00
[architectural overview ](../overview/architecture-overview.md ).
2017-11-12 22:14:15 +01:00
Zulip is a web application built using the
[Django framework ](https://www.djangoproject.com/ ), and some of the
processes listed in this tutorial, such as database migrations and
tests, use Django's tooling.
2019-09-30 19:37:56 +02:00
Zulip's [directory structure ](../overview/directory-structure.md )
2018-11-30 00:48:13 +01:00
will also be helpful to review when creating a new feature. Many
aspects of the structure will be familiar to Django developers. Visit
2021-11-05 20:09:57 +01:00
[Django's documentation ](https://docs.djangoproject.com/en/3.2/#index-first-steps )
2018-11-30 00:48:13 +01:00
for more information about how Django projects are typically
2021-08-20 21:53:28 +02:00
organized. And finally, the
2019-09-30 19:37:56 +02:00
[message sending ](../subsystems/sending-messages.md ) documentation on
2018-11-30 00:48:13 +01:00
the additional complexity involved in sending messages.
2017-02-10 10:09:31 +01:00
2020-08-11 01:47:54 +02:00
## General process
2017-08-14 17:25:48 +02:00
### Files impacted
This tutorial will walk through adding a new feature to a Realm (an
organization in Zulip). The following files are involved in the process:
**Backend**
2021-08-20 22:54:08 +02:00
2019-02-03 22:24:18 +01:00
- `zerver/models.py` : Defines the database model.
2017-08-14 17:25:48 +02:00
- `zerver/views/realm.py` : The view function that implements the API endpoint
for editing realm objects.
2022-04-14 23:57:15 +02:00
- `zerver/actions/realm_settings.py` : Contains code for updating and interacting with the database.
2017-08-14 17:25:48 +02:00
- `zerver/lib/events.py` : Ensures that the state Zulip sends to clients is always
consistent and correct.
**Frontend**
2021-08-20 22:54:08 +02:00
2019-07-12 00:52:56 +02:00
- `static/templates/settings/organization_permissions_admin.hbs` : defines
2021-08-20 22:54:08 +02:00
the structure of the admin permissions page (checkboxes for each organization
permission setting).
2017-08-14 17:25:48 +02:00
- `static/js/settings_org.js` : handles organization setting form submission.
- `static/js/server_events_dispatch.js` : handles events coming from the server
(ex: pushing an organization change to other open browsers and updating
the application's state).
2020-08-11 01:47:54 +02:00
**Backend testing**
2021-08-20 22:54:08 +02:00
2017-08-14 17:25:48 +02:00
- `zerver/tests/test_realm.py` : end-to-end API tests for updating realm settings.
- `zerver/tests/test_events.py` : tests for possible race bugs in the
zerver/lib/events.py implementation.
2020-08-11 01:47:54 +02:00
**Frontend testing**
2021-08-20 22:54:08 +02:00
2021-04-01 14:37:19 +02:00
- `frontend_tests/puppeteer_tests/admin.ts` : end-to-end tests for the organization
2017-08-14 17:25:48 +02:00
admin settings pages.
- `frontend_tests/node_tests/dispatch.js`
2016-05-15 18:28:38 +02:00
2021-11-01 16:38:19 +01:00
**Documentation**
- `zerver/openapi/zulip.yaml` : OpenAPI definitions for the Zulip REST API.
- `templates/zerver/api/changelog.md` : documentation listing all changes to the Zulip Server API.
- `templates/zerver/help/...` : end user facing documentation (Help Center) for the application.
2016-05-15 18:28:38 +02:00
### Adding a field to the database
**Update the model:** The server accesses the underlying database in
2017-08-14 17:25:48 +02:00
`zerver/models.py` . Add a new field in the appropriate class.
2016-05-15 18:28:38 +02:00
2017-11-12 22:14:15 +01:00
**Create and run the migration:** To create and apply a migration, run the
following commands:
2016-05-15 18:28:38 +02:00
2021-08-20 07:09:04 +02:00
```bash
2016-07-16 03:11:42 +02:00
./manage.py makemigrations
./manage.py migrate
```
2016-05-15 18:28:38 +02:00
2017-11-12 22:14:15 +01:00
You can read our
2019-09-30 19:37:56 +02:00
[database migration documentation ](../subsystems/schema-migrations.md )
2017-11-12 22:14:15 +01:00
to learn more about creating and applying database migrations.
2017-09-12 17:19:54 +02:00
**Test your changes:** Once you've run the migration, flush memcached
on your development server (`./scripts/setup/flush-memcached`) and then
2022-02-16 01:39:15 +01:00
[restart the development server ](../development/remote.md#running-the-development-server )
2017-08-14 17:25:48 +02:00
to avoid interacting with cached objects.
2016-05-15 18:28:38 +02:00
### Backend changes
2017-08-14 17:25:48 +02:00
We have a framework that automatically handles many of the steps for the
most common types of UserProfile and Realm settings. We refer to this as the
`property_types` framework. However, it is valuable to understand
the flow of events even if the `property_types` framework means you don't
have to write much code for a new setting.
2017-04-19 05:27:57 +02:00
2016-05-15 18:28:38 +02:00
**Database interaction:** Add any necessary code for updating and
2022-04-14 23:57:15 +02:00
interacting with the database in `zerver/actions/realm_settings.py` . It should
2016-05-15 18:28:38 +02:00
update the database and send an event announcing the change.
**Application state:** Modify the `fetch_initial_state_data` and
2017-02-22 09:33:33 +01:00
`apply_event` functions in `zerver/lib/events.py` to update the state
2016-05-15 18:28:38 +02:00
based on the event you just created.
**Backend implementation:** Make any other modifications to the backend
2017-08-14 17:25:48 +02:00
required for your feature to do what it's supposed to do (this will
be unique to the feature you're implementing).
2016-05-15 18:28:38 +02:00
2017-09-16 09:38:32 +02:00
**New views:** Add any new application views to `zproject/urls.py` , or
2017-08-14 17:25:48 +02:00
update the appropriate existing view in `zerver/views/` . This
2016-07-16 03:11:42 +02:00
includes both views that serve HTML (new pages on Zulip) as well as new
API endpoints that serve JSON-formatted data.
2016-05-15 18:28:38 +02:00
**Testing:** At the very least, add a test of your event data flowing
2017-08-14 17:25:48 +02:00
through the system in `test_events.py` and an API test (e.g. for a
2017-04-19 05:27:57 +02:00
Realm setting, in `test_realm.py` ).
2016-05-15 18:28:38 +02:00
### Frontend changes
2019-03-28 20:55:05 +01:00
**JavaScript/TypeScript:** Zulip's JavaScript and TypeScript sources are
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 in `static/js/server_events_dispatch.js` .
2016-05-15 18:28:38 +02:00
**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 Jinja2
2018-04-22 07:02:19 +02:00
templates located in `templates/zerver/app` . For JavaScript, Zulip uses
2016-05-15 18:28:38 +02:00
Handlebars templates located in `static/templates` . Templates are
precompiled as part of the build/deploy process.
2017-03-01 05:41:40 +01:00
Zulip is fully internationalized, so when writing both HTML templates
2022-02-20 14:52:57 +01:00
or JavaScript/TypeScript/Python code that generates user-facing strings, be sure to
2019-09-30 19:37:56 +02:00
[tag those strings for translation ](../translating/translating.md ).
2017-03-01 05:41:40 +01:00
2016-05-15 18:28:38 +02:00
**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
2020-08-31 03:39:34 +02:00
headless Chromium browser using Puppeteer and are located in
`frontend_tests/puppeteer_tests/` . The unit tests use Node's `assert`
2016-06-28 05:51:42 +02:00
module are located in `frontend_tests/node_tests/` . For more
2017-08-14 17:25:48 +02:00
information on writing and running tests, see the
2019-09-30 19:37:56 +02:00
[testing documentation ](../testing/testing.md ).
2016-05-15 18:28:38 +02:00
2017-03-06 00:02:17 +01:00
### Documentation changes
2021-11-01 16:38:19 +01:00
After implementing the new feature, you should document it and update
any existing documentation that might be relevant to the new feature.
For detailed information on the kinds of documentation Zulip has, see
[Documentation ](../documentation/overview.md ).
2021-12-17 15:59:58 +01:00
**Help center documentation:** You will likely need to at least update,
extend and link to `/help/` articles that are related to your new
2022-01-20 19:49:27 +01:00
feature. [Writing help center articles ](../documentation/helpcenter.md )
2021-12-17 15:59:58 +01:00
provides more detailed information about writing and editing feature
`/help/` articles.
2021-11-01 16:38:19 +01:00
**API documentation:** A new feature will probably impact the REST API
documentation as well, which will mean updating `zerver/openapi/zulip.yaml`
and modifying `templates/zerver/api/changelog.md` for a new feature
level. [Documenting REST API endpoints ](../documentation/api.md )
explains Zulip's API documentation system and provides a step by step
guide to adding or updating documentation for an API endpoint.
2017-03-06 00:02:17 +01:00
2020-10-23 02:43:28 +02:00
## Example feature
2016-05-15 18:28:38 +02:00
This example describes the process of adding a new setting to Zulip: a
2017-08-14 17:25:48 +02:00
flag that allows an admin to require topics on stream messages (the default
behavior is that topics can have no subject). This flag is an
2021-08-20 22:54:08 +02:00
actual Zulip feature. You can review [the original commit ](https://github.com/zulip/zulip/pull/5660/commits/aeeb81d3ff0e0cc201e891cec07e1d2cd0a2060d )
2017-08-14 17:25:48 +02:00
in the Zulip repo. (This commit displays the work of setting up a checkbox
for the feature on the admin settings page, communicating and saving updates
to the setting to the database, and updating the state of the application
after the setting is updated. For the code that accomplishes the underlying
2021-08-20 22:54:08 +02:00
task of requiring messages to have a topic, you can [view this commit ](https://github.com/zulip/zulip/commit/90e2f5053f5958b44ea9b2362cadcb076deaa975 ).)
2016-05-15 18:28:38 +02:00
2016-07-16 03:11:42 +02:00
### Update the model
2016-05-15 18:28:38 +02:00
First, update the database and model to store the new setting. Add a new
2017-08-14 17:25:48 +02:00
boolean field, `mandatory_topics` , to the Realm model in
2016-05-15 18:28:38 +02:00
`zerver/models.py` .
2021-08-20 22:54:08 +02:00
```diff
2021-08-20 07:09:04 +02:00
# zerver/models.py
2017-08-14 17:25:48 +02:00
2021-08-20 07:09:04 +02:00
class Realm(models.Model):
# ...
emails_restricted_to_domains: bool = models.BooleanField(default=True)
invite_required: bool = models.BooleanField(default=False)
+ mandatory_topics: bool = models.BooleanField(default=False)
2017-04-26 22:18:31 +02:00
```
2017-04-19 05:27:57 +02:00
The Realm model also contains an attribute, `property_types` , which
2017-08-14 17:25:48 +02:00
other backend functions use to handle most realm settings without any custom
2017-04-19 05:27:57 +02:00
code for the setting (more on this process below). The attribute is a
dictionary, where the key is the name of the realm field and the value
is the field's type. Add the new field to the `property_types`
dictionary.
2021-08-20 22:54:08 +02:00
```diff
2021-08-20 07:09:04 +02:00
# zerver/models.py
2017-08-14 17:25:48 +02:00
2021-08-20 07:09:04 +02:00
class Realm(models.Model)
# ...
# Define the types of the various automatically managed properties
property_types = dict(
add_custom_emoji_policy=int,
allow_edit_history=bool,
# ...
+ mandatory_topics=bool,
# ...
2017-08-14 17:25:48 +02:00
```
2017-04-19 05:27:57 +02:00
2017-08-14 17:25:48 +02:00
**The majority of realm settings can be included in
2021-08-20 21:53:28 +02:00
`property_types` .** However, there are some properties that need custom
logic and thus cannot use this framework. For example:
2017-04-19 05:27:57 +02:00
2021-08-20 21:45:39 +02:00
- The realm `authentication_methods` attribute is a bitfield and needs
2021-08-20 22:54:08 +02:00
additional code for validation and updating.
2021-08-20 21:45:39 +02:00
- The `allow_message_editing` and `message_content_edit_limit_seconds`
2021-08-20 22:54:08 +02:00
fields depend on one another, so they are also handled separately and
not included in `property_types` .
2017-04-19 05:27:57 +02:00
When creating a realm property that is not a boolean, Text or
integer field, or when adding a field that is dependent on other fields,
2017-08-14 17:25:48 +02:00
do not add the field to the `property_types` dictionary. The steps
below will point out where to write additional code for these cases.
2016-07-16 03:11:42 +02:00
### Create the migration
2017-11-12 22:14:15 +01:00
Create the migration file using the Django `makemigrations` command:
`./manage.py makemigrations` . Make sure to commit the generated file to git:
2017-08-14 17:25:48 +02:00
`git add zerver/migrations/NNNN_realm_mandatory_topics.py`
2016-07-16 03:11:42 +02:00
(NNNN is a number that is equal to the number of migrations.)
2017-01-06 00:06:34 +01:00
If you run into problems, the
2021-11-05 20:09:57 +01:00
[Django migration documentation ](https://docs.djangoproject.com/en/3.2/topics/migrations/ )
2017-01-06 00:06:34 +01:00
is helpful.
2016-07-16 03:11:42 +02:00
### Test your migration changes
2017-11-12 22:14:15 +01:00
Apply the migration using Django's `migrate` command: `./manage.py migrate` .
2016-07-16 03:11:42 +02:00
Output:
2021-08-20 22:54:08 +02:00
2021-08-20 07:09:04 +02:00
```console
2016-07-16 03:11:42 +02:00
shell $ ./manage.py migrate
Operations to perform:
Synchronize unmigrated apps: staticfiles, analytics, pipeline
Apply all migrations: zilencer, confirmation, sessions, guardian, zerver, sites, auth, contenttypes
Synchronizing apps without migrations:
Creating tables...
Running deferred SQL...
Installing custom SQL...
Running migrations:
Rendering model states... DONE
2017-08-14 17:25:48 +02:00
Applying zerver.NNNN_realm_mandatory_topics... OK
2016-07-16 03:11:42 +02:00
```
2017-08-14 17:25:48 +02:00
Once you've run the migration, restart memcached on your development
2022-02-16 01:39:15 +01:00
server (`/etc/init.d/memcached restart`) and then [restart the development server ](../development/remote.md#running-the-development-server )
2017-08-14 17:25:48 +02:00
to avoid interacting with cached objects.
2016-07-16 03:11:42 +02:00
### Handle database interactions
2016-05-15 18:28:38 +02:00
2017-08-14 17:25:48 +02:00
Next, we will implement the backend part of this feature.
2016-08-03 22:04:44 +02:00
Like typical apps, we will need our backend to update the database and
send some response to the client that made the request.
2017-08-14 17:25:48 +02:00
Beyond that, we need to orchestrate notifications about the setting change
2021-08-20 22:54:08 +02:00
to _other_ clients (or other users, if you will). Clients
2017-08-14 17:25:48 +02:00
find out about settings through two closely related code paths. When a client
first contacts the server, the server sends the client its
2017-04-19 05:27:57 +02:00
initial state. Subsequently, clients subscribe to "events," which can
2017-08-14 17:25:48 +02:00
(among other things) indicate that settings have changed.
For the backend piece, we will need our action to make a call to `send_event`
2017-04-19 05:27:57 +02:00
to send the event to clients that are active. We will also need to
2017-05-14 07:49:35 +02:00
modify `fetch_initial_state_data` so that the new field is passed to
2019-09-30 19:37:56 +02:00
clients. See [our event system docs ](../subsystems/events-system.md ) for all the
2017-04-19 05:27:57 +02:00
gory details.
2016-08-03 22:04:44 +02:00
Anyway, getting back to implementation details...
2017-08-14 17:25:48 +02:00
If you are working on a feature that is in the realm `property_types`
2022-04-14 23:57:15 +02:00
dictionary, you will not need to add code to `zerver/actions/realm_settings.py` , but
2017-08-14 17:25:48 +02:00
we will describe what the process in that file does:
2022-04-14 23:57:15 +02:00
In `zerver/actions/realm_settings.py` , the function `do_set_realm_property` takes
2017-04-19 05:27:57 +02:00
in the name of a realm property to update and the value it should
have. This function updates the database and triggers an event to
notify clients about the change. It uses the field's type, specified
in the `Realm.property_types` dictionary, to validate the type of the
value before updating the property; this is primarily an assertion to
help catch coding mistakes, not to check for bad user input.
After updating the given realm field, `do_set_realm_property` creates
an 'update' event with the name of the property and the new value. It
then calls `send_event` , passing the event and the list of users whose
browser sessions should be notified as the second argument. The latter
argument can be a single user (if the setting is a personal one, like
time display format), members in a particular stream only or all
active users in a realm.
2016-05-15 18:28:38 +02:00
2021-08-20 07:09:04 +02:00
```python
2022-04-14 23:57:15 +02:00
# zerver/actions/realm_settings.py
2021-08-20 07:09:04 +02:00
def do_set_realm_property(
realm: Realm, name: str, value: Any, *, acting_user: Optional[UserProfile]
) -> None:
"""Takes in a realm object, the name of an attribute to update, the
value to update and and the user who initiated the update.
"""
property_type = Realm.property_types[name]
assert isinstance(value, property_type), (
'Cannot update %s: %s is not an instance of %s' % (
name, value, property_type,))
setattr(realm, name, value)
realm.save(update_fields=[name])
event = dict(
type='realm',
op='update',
property=name,
value=value,
)
send_event(realm, event, active_user_ids(realm))
```
2017-04-19 05:27:57 +02:00
If the new realm property being added does not fit into the
2017-08-14 17:25:48 +02:00
`property_types` framework (such as the `authentication_methods`
field), you'll need to create a new function to explicitly update this
field and send an event. For example:
2017-04-19 05:27:57 +02:00
2021-08-20 07:09:04 +02:00
```python
2022-04-14 23:57:15 +02:00
# zerver/actions/realm_settings.p
2021-08-20 07:09:04 +02:00
def do_set_realm_authentication_methods(
realm: Realm, authentication_methods: Dict[str, bool], *, acting_user: Optional[UserProfile]
) -> None:
for key, value in list(authentication_methods.items()):
index = getattr(realm.authentication_methods, key).number
realm.authentication_methods.set_bit(index, int(value))
realm.save(update_fields=['authentication_methods'])
event = dict(
type="realm",
op="update_dict",
property='default',
data=dict(authentication_methods=realm.authentication_methods_dict())
)
send_event(realm, event, active_user_ids(realm))
```
2016-05-15 18:28:38 +02:00
2016-07-16 03:11:42 +02:00
### Update application state
2017-08-14 17:25:48 +02:00
`zerver/lib/events.py` contains code to ensure that your new setting is included
in the data sent down to clients: both when a new client is loaded
and when changes happen. This file also automatically
handles realm settings in the `property_types` dictionary, so you would
not need to change this file if your setting fits that framework.
The `fetch_initial_state_data` function is responsible for sending data when
a client is loaded (data added to the `state` here will be available both
in `page_params` in the browser, as well as to API clients like the mobile
apps). The `apply_event` function in `zerver/lib/events.py` is important for
making sure the `state` is always correct, even in the event of rare
race conditions.
2017-04-19 05:27:57 +02:00
2021-08-20 07:09:04 +02:00
```python
# zerver/lib/events.py
2016-05-15 18:28:38 +02:00
2021-08-20 07:09:04 +02:00
def fetch_initial_state_data(user_profile, event_types, queue_id, include_subscribers=True):
# ...
if want('realm'):
2017-04-19 05:27:57 +02:00
for property_name in Realm.property_types:
state['realm_' + property_name] = getattr(user_profile.realm, property_name)
state['realm_authentication_methods'] = user_profile.realm.authentication_methods_dict()
state['realm_allow_message_editing'] = user_profile.realm.allow_message_editing
# ...
2016-05-15 18:28:38 +02:00
2021-08-20 07:09:04 +02:00
def apply_event
user_profile: UserProfile,
# ...
) -> None:
for event in events:
2016-05-15 18:28:38 +02:00
# ...
elif event['type'] == 'realm':
field = 'realm_' + event['property']
state[field] = event['value']
2017-04-19 05:27:57 +02:00
# ...
2021-08-20 07:09:04 +02:00
```
2017-04-19 05:27:57 +02:00
2017-08-14 17:25:48 +02:00
If your new realm property fits the `property_types`
2017-04-19 05:27:57 +02:00
framework, you don't need to change `fetch_initial_state_data` or
2017-08-14 17:25:48 +02:00
`apply_event` . However, if you are adding a
2017-05-14 07:49:35 +02:00
property that is handled separately, you will need to explicitly add
2017-08-14 17:25:48 +02:00
the property to the `state` dictionary in the `fetch_initial_state_data`
function. E.g., for `authentication_methods` :
2021-08-20 07:09:04 +02:00
```python
# zerver/lib/events.py
2017-04-19 05:27:57 +02:00
2021-08-20 07:09:04 +02:00
def fetch_initial_state_data(user_profile, event_types, queue_id, include_subscribers=True):
# ...
if want('realm'):
# ...
state['realm_authentication_methods'] = user_profile.realm.authentication_methods_dict()
# ...
```
2017-04-19 05:27:57 +02:00
For this setting, one won't need to change `apply_event` since its
default code for `realm` event types handles this case correctly, but
2017-05-14 07:49:35 +02:00
for a totally new type of feature, a few lines in that function may be
needed.
2016-05-15 18:28:38 +02:00
2016-07-16 03:11:42 +02:00
### Add a new view
2017-04-19 05:27:57 +02:00
You will need to add a view for clients to access that will call the
`actions.py` code to update the database. This example feature
2017-04-26 22:18:31 +02:00
adds a new parameter that will be sent to clients when the
2017-04-19 05:27:57 +02:00
application loads and should be accessible via JavaScript. There is
already a view that does this for related flags: `update_realm` in
`zerver/views/realm.py` . So in this case, we can add our code to the
existing view instead of creating a new one.
2016-05-15 18:28:38 +02:00
2017-08-14 17:25:48 +02:00
You'll need to add a parameter for the new field to the `update_realm`
function in `zerver/views/realm.py` (and add the appropriate mypy type
annotation).
2017-04-19 05:27:57 +02:00
2021-08-20 22:54:08 +02:00
```diff
2021-08-20 07:09:04 +02:00
# zerver/views/realm.py
2017-08-14 17:25:48 +02:00
2020-05-09 00:10:17 +02:00
def update_realm(
request: HttpRequest,
user_profile: UserProfile,
2021-05-08 00:12:18 +02:00
name: Optional[str] = REQ(str_validator=check_string, default=None),
2020-05-09 00:10:17 +02:00
# ...
2021-04-07 22:00:44 +02:00
+ mandatory_topics: Optional[bool] = REQ(json_validator=check_bool, default=None),
2020-05-09 00:10:17 +02:00
# ...
):
# ...
2017-08-14 17:25:48 +02:00
```
2017-04-19 05:27:57 +02:00
2017-08-14 17:25:48 +02:00
If this feature fits the `property_types` framework and does
2017-04-19 05:27:57 +02:00
not require additional validation, this is the only change to make
to `zerver/views/realm.py` .
Text fields or other realm properties that need additional validation
can be handled at the beginning of `update_realm` .
2021-08-20 07:09:04 +02:00
```python
# zerver/views/realm.py
2017-08-14 17:25:48 +02:00
2021-08-20 07:09:04 +02:00
# Additional validation/error checking beyond types go here, so
# the entire request can succeed or fail atomically.
if default_language is not None and default_language not in get_available_language_codes():
raise JsonableError(_("Invalid language '%s'" % (default_language,)))
if description is not None and len(description) > 100:
raise JsonableError(_("Realm description cannot exceed 100 characters."))
# ...
```
2017-04-19 05:27:57 +02:00
2017-08-14 17:25:48 +02:00
The code in `update_realm` loops through the `property_types` dictionary
2017-04-19 05:27:57 +02:00
and calls `do_set_realm_property` on any property to be updated from
2017-08-14 17:25:48 +02:00
the request.
If the new feature is not in `property_types` , you will need to write code
to call the function you wrote in `actions.py` that updates the database
with the new value. E.g., for `authentication_methods` , we created
`do_set_realm_authentication_methods` , which we will call here:
2016-05-15 18:28:38 +02:00
2021-08-20 07:09:04 +02:00
```python
# zerver/views/realm.py
2016-05-15 18:28:38 +02:00
2021-08-20 07:09:04 +02:00
# import do_set_realm_authentication_methods from actions.py
2022-04-14 23:57:15 +02:00
from zerver.actions.realm_settings import (
2022-09-22 10:53:37 +02:00
do_reactivate_realm,
2021-08-20 07:09:04 +02:00
do_set_realm_authentication_methods,
2017-04-19 05:27:57 +02:00
# ...
2021-08-20 07:09:04 +02:00
)
# ...
# ...
if authentication_methods is not None and realm.authentication_methods_dict() != authentication_methods:
do_set_realm_authentication_methods(realm, authentication_methods, acting_user=user_profile)
data['authentication_methods'] = authentication_methods
# ...
```
2016-05-15 18:28:38 +02:00
2017-08-14 17:25:48 +02:00
This completes the backend implementation. A great next step is to
2017-11-12 22:14:15 +01:00
write automated backend tests for your new feature.
2017-07-04 23:18:29 +02:00
2020-08-11 01:47:54 +02:00
### Backend tests
2017-08-14 17:25:48 +02:00
2017-07-04 23:18:29 +02:00
To test the new setting syncs correctly with the `property_types`
framework, one usually just needs to add a line in each of
2017-04-19 05:27:57 +02:00
`test_events.py` and `test_realm.py` with a list of values to switch
2021-08-20 21:53:28 +02:00
between in the test. In the case of a boolean field, no action is
2017-07-04 23:18:29 +02:00
required, because those tests will correctly assume that the only
values to test are `True` and `False` .
2017-08-14 17:25:48 +02:00
In `test_events.py` , the function that runs tests for the `property_types`
framework is `do_set_realm_property_test` , and in `test_realm.py` , it is
`do_test_realm_update_api` .
2017-07-04 23:18:29 +02:00
One still needs to add a test for whether the setting actually
2017-11-12 22:14:15 +01:00
controls the feature it is supposed to control, however (e.g. for this
example feature, whether sending a message without a topic fails with
the setting enabled).
2019-09-30 19:37:56 +02:00
Visit Zulip's [Django testing ](../testing/testing-with-django.md )
2017-11-12 22:14:15 +01:00
documentation to learn more about the backend testing framework.
2016-05-15 18:28:38 +02:00
2020-09-12 20:41:35 +02:00
### Update the frontend
2016-05-15 18:28:38 +02:00
2020-09-19 01:39:07 +02:00
After completing the process of adding a new feature on the backend,
2020-09-12 20:41:35 +02:00
you should make the required frontend changes: in this case, a checkbox needs
2016-05-15 18:28:38 +02:00
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.
2018-03-31 14:17:06 +02:00
To add the checkbox to the admin page, modify the relevant template in
`static/templates/settings/` , which can be
2019-07-12 00:52:56 +02:00
`organization_permissions_admin.hbs` or `organization_settings_admin.hbs`
2017-08-14 17:25:48 +02:00
(omitted here since it is relatively straightforward).
2020-08-26 02:12:38 +02:00
If you're adding a non-checkbox field, you'll need to specify the type
of the field via the `data-setting-widget-type` attribute in the HTML
template.
2017-08-14 17:25:48 +02:00
Then add the new form control in `static/js/admin.js` .
2021-08-20 22:54:08 +02:00
```diff
2021-08-20 07:09:04 +02:00
// static/js/admin.js
function _setup_page() {
var options = {
realm_name: page_params.realm_name,
realm_description: page_params.realm_description,
realm_emails_restricted_to_domains: page_params.realm_emails_restricted_to_domains,
realm_invite_required: page_params.realm_invite_required,
// ...
+ realm_mandatory_topics: page_params.mandatory_topics,
// ...
2017-08-14 17:25:48 +02:00
```
The JavaScript code for organization settings and permissions can be found in
`static/js/settings_org.js` .
2018-03-31 14:17:06 +02:00
In frontend, we have split the `property_types` into three objects:
2017-08-14 17:25:48 +02:00
2018-03-31 14:17:06 +02:00
- `org_profile` : This contains properties for the "organization
2021-08-20 22:54:08 +02:00
profile" settings page.
2017-08-14 17:25:48 +02:00
2018-03-31 14:17:06 +02:00
- `org_settings` : This contains properties for the "organization
2021-08-20 22:54:08 +02:00
settings" page. Settings belonging to this section generally
decide what features should be available to a user like deleting a
message, message edit history etc. Our `mandatory_topics` feature
belongs in this section.
2018-03-31 14:17:06 +02:00
- `org_permissions` : This contains properties for the "organization
2021-08-20 22:54:08 +02:00
permissions" section. These properties control security controls
like who can join the organization and whether normal users can
create streams or upload custom emoji.
2018-03-31 14:17:06 +02:00
2020-03-17 13:57:10 +01:00
Once you've determined whether the new setting belongs, the next step
2018-03-31 14:17:06 +02:00
is to find the right subsection of that page to put the setting
in. For example in this case of `mandatory_topics` it will lie in
2020-08-25 22:45:37 +02:00
"Other settings" (`other_settings`) subsection.
2018-03-31 14:17:06 +02:00
2021-08-20 22:54:08 +02:00
_If you're not sure in which section your feature belongs, it's
2021-11-18 07:25:55 +01:00
better to discuss it in
2021-12-09 20:15:18 +01:00
[the Zulip development community ](https://zulip.com/development-community/ )
2021-08-20 22:54:08 +02:00
before implementing it._
2018-03-31 14:17:06 +02:00
2019-05-06 18:41:36 +02:00
Note that some settings, like `realm_msg_edit_limit_setting` ,
require special treatment, because they don't match the common
2021-08-20 21:53:28 +02:00
pattern. We can't extract the property name and compare the value of
2018-03-31 14:17:06 +02:00
such input elements with those in `page_params` , so we have to
manually handle such situations in a couple key functions:
2017-08-14 17:25:48 +02:00
2018-03-31 14:17:06 +02:00
- `settings_org.get_property_value` : This processes the property name
2021-08-20 22:54:08 +02:00
when it doesn't match a corresponding key in `page_params` , and
returns the current value of that property, which we can use to
compare and set the values of corresponding DOM element.
2017-08-14 17:25:48 +02:00
2018-03-31 14:17:06 +02:00
- `settings_org.update_dependent_subsettings` : This handles settings
2021-08-20 22:54:08 +02:00
whose value and state depend on other elements. For example,
`realm_waiting_period_threshold` is only shown for with the right
state of `realm_waiting_period_setting` .
2016-05-15 18:28:38 +02:00
2017-05-31 22:04:19 +02:00
Finally, update `server_events_dispatch.js` to handle related events coming from
2017-08-14 17:25:48 +02:00
the server. There is an object, `realm_settings` , in the function
`dispatch_normal_event` . The keys in this object are setting names and the
values are the UI updating functions to run when an event has occurred.
2016-05-15 18:28:38 +02:00
2018-03-31 14:17:06 +02:00
If there is no relevant UI change to make other than in settings page
itself, the value should be `noop` (this is the case for
`mandatory_topics` , since this setting only has an effect on the
backend, so no UI updates are required.).
However, if you had written a function to update the UI after a given
setting has changed, your function should be referenced in the
2021-08-20 21:53:28 +02:00
`realm_settings` of `server_events_dispatch.js` . See for example
2018-03-31 14:17:06 +02:00
`settings_emoji.update_custom_emoji_ui` .
2016-05-15 18:28:38 +02:00
2021-08-20 22:54:08 +02:00
```diff
2021-08-20 07:09:04 +02:00
// static/js/server_events_dispatch.js
function dispatch_normal_event(event) {
switch (event.type) {
// ...
case 'realm':
var realm_settings = {
add_custom_emoji_policy: settings_emoji.update_custom_emoji_ui,
allow_edit_history: noop,
// ...
+ mandatory_topics: noop,
// ...
};
2017-08-14 17:25:48 +02:00
```
2018-03-31 14:17:06 +02:00
Checkboxes and other common input elements handle the UI updates
automatically through the logic in `settings_org.sync_realm_settings` .
2017-08-14 17:25:48 +02:00
The rest of the `dispatch_normal_events` function updates the state of the
application if an update event has occurred on a realm property and runs
the associated function to update the application's UI, if necessary.
2018-03-31 14:17:06 +02:00
Here are few important cases you should consider when testing your changes:
- For organization settings where we have a "save/discard" model, make
sure both the "Save" and "Discard changes" buttons are working
properly.
- If your setting is dependent on another setting, carefully check
2021-08-20 21:53:28 +02:00
that both are properly synchronized. For example, the input element
2018-03-31 14:17:06 +02:00
for `realm_waiting_period_threshold` is shown only when we have
selected the custom time limit option in the
2020-08-25 22:49:11 +02:00
`realm_waiting_period_setting` dropdown.
2018-03-31 14:17:06 +02:00
- Do some manual testing for the real-time synchronization of input
elements across the browsers and just like "Discard changes" button,
check whether dependent settings are synchronized properly (this is
easy to do by opening two browser windows to the settings page, and
making changes in one while watching the other).
- Each subsection has independent "Save" and "Discard changes"
buttons, so changes and saving in one subsection shouldn't affect
the others.
2020-09-12 20:41:35 +02:00
### Frontend tests
2017-08-14 17:25:48 +02:00
2020-09-12 20:41:35 +02:00
A great next step is to write frontend tests. There are two types of
2019-09-30 19:37:56 +02:00
frontend tests: [node-based unit tests ](../testing/testing-with-node.md ) and
2020-08-31 03:39:34 +02:00
[Puppeteer end-to-end tests ](../testing/testing-with-puppeteer.md ).
2017-08-14 17:25:48 +02:00
At the minimum, if you created a new function to update UI in
`settings_org.js` , you will need to mock that function in
`frontend_tests/node_tests/dispatch.js` . Add the name of the UI
function you created to the following object with `noop` as the value:
2021-08-20 07:09:04 +02:00
```js
// frontend_tests/node_tests/dispatch.js
2017-08-14 17:25:48 +02:00
2021-08-20 07:09:04 +02:00
set_global('settings_org', {
update_email_change_display: noop,
update_name_change_display: noop,
});
```
2016-05-15 18:28:38 +02:00
2017-08-14 17:25:48 +02:00
Beyond that, you should add any applicable tests that verify the
behavior of the setting you just created.
2017-03-06 00:02:17 +01:00
### Update documentation
2021-11-01 16:38:19 +01:00
Nice job! You've added a new feature to Zulip that will improve user
and contributor experiences with the app, which is why it's really
important to make sure that your new feature is well documented.
This example feature adds new functionality that requires messages to
have topics if the setting is enabled. A recommended way to document
this feature would be to update and/or augment Zulip's existing
2021-12-17 15:59:58 +01:00
[help center documentation ](https://zulip.com/help/ ) to reflect your
changes and additions.
2021-11-01 16:38:19 +01:00
At the very least, this will involve modifying (or adding) a Markdown
file documenting the feature to `templates/zerver/help/` in the main
Zulip server repository, where the source for Zulip's end user
documentation is stored. Details about writing, editing and testing
these Markdown files can be found in:
2022-01-20 19:49:27 +01:00
[Writing help center articles ](../documentation/helpcenter.md ).
2021-11-01 16:38:19 +01:00
Also, new features will often impact Zulip's REST API documentation,
which is found in `zerver/openapi/zulip.yaml` . You may have noticed
this during the testing process as the Zulip test suite should fail if
there is a change to the API without a corresponding update to the
documentation.
The best way to understand writing and updating Zulip's API
documentation is to read more about Zulip's
[REST API documentation process ](../documentation/api.md )
and [OpenAPI configuration ](../documentation/openapi.md ).
In particular, if there is an API change, **make sure** you document
your new feature in `templates/zerver/api/changelog.md` and bump the
`API_FEATURE_LEVEL` in `version.py` . The API feature level allows the
developers of mobile clients and other tools using the Zulip API to
programmatically determine whether the Zulip server they are
interacting with supports a given feature; see the
[Zulip release lifecycle ](../overview/release-lifecycle.md ).