Compare commits

..

2 Commits

Author SHA1 Message Date
Tim Abbott
d30fcac192 Release Zulip Server 6.0-beta1. 2022-10-12 22:46:39 -07:00
Tim Abbott
cc60e8a0a0 tools: Don't display release candidate as forks. 2022-10-12 22:46:37 -07:00
1212 changed files with 37657 additions and 46194 deletions

View File

@@ -1,5 +1,4 @@
{ {
"root": true,
"env": { "env": {
"es2020": true, "es2020": true,
"node": true "node": true
@@ -51,7 +50,13 @@
"import/newline-after-import": "error", "import/newline-after-import": "error",
"import/no-self-import": "error", "import/no-self-import": "error",
"import/no-useless-path-segments": "error", "import/no-useless-path-segments": "error",
"import/order": ["error", {"alphabetize": {"order": "asc"}, "newlines-between": "always"}], "import/order": [
"error",
{
"alphabetize": {"order": "asc"},
"newlines-between": "always"
}
],
"import/unambiguous": "error", "import/unambiguous": "error",
"lines-around-directive": "error", "lines-around-directive": "error",
"new-cap": "error", "new-cap": "error",
@@ -96,10 +101,15 @@
"no-useless-concat": "error", "no-useless-concat": "error",
"no-useless-constructor": "error", "no-useless-constructor": "error",
"no-var": "error", "no-var": "error",
"object-shorthand": ["error", "always", {"avoidExplicitReturnArrows": true}], "object-shorthand": "error",
"one-var": ["error", "never"], "one-var": ["error", "never"],
"prefer-arrow-callback": "error", "prefer-arrow-callback": "error",
"prefer-const": ["error", {"ignoreReadBeforeAssign": true}], "prefer-const": [
"error",
{
"ignoreReadBeforeAssign": true
}
],
"radix": "error", "radix": "error",
"sort-imports": ["error", {"ignoreDeclarationSort": true}], "sort-imports": ["error", {"ignoreDeclarationSort": true}],
"spaced-comment": ["error", "always", {"markers": ["/"]}], "spaced-comment": ["error", "always", {"markers": ["/"]}],
@@ -108,10 +118,11 @@
"unicorn/explicit-length-check": "off", "unicorn/explicit-length-check": "off",
"unicorn/filename-case": "off", "unicorn/filename-case": "off",
"unicorn/no-await-expression-member": "off", "unicorn/no-await-expression-member": "off",
"unicorn/no-negated-condition": "off", "unicorn/no-nested-ternary": "off",
"unicorn/no-null": "off", "unicorn/no-null": "off",
"unicorn/no-process-exit": "off", "unicorn/no-process-exit": "off",
"unicorn/no-useless-undefined": "off", "unicorn/no-useless-undefined": "off",
"unicorn/number-literal-case": "off",
"unicorn/numeric-separators-style": "off", "unicorn/numeric-separators-style": "off",
"unicorn/prefer-module": "off", "unicorn/prefer-module": "off",
"unicorn/prefer-node-protocol": "off", "unicorn/prefer-node-protocol": "off",
@@ -176,16 +187,18 @@
{"allowExpressions": true} {"allowExpressions": true}
], ],
"@typescript-eslint/member-ordering": "error", "@typescript-eslint/member-ordering": "error",
"@typescript-eslint/no-duplicate-imports": "error", "@typescript-eslint/no-duplicate-imports": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-parameter-properties": "error", "@typescript-eslint/no-parameter-properties": "error",
"@typescript-eslint/no-unnecessary-qualifier": "error", "@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unused-vars": ["error", {"ignoreRestSiblings": true}],
"@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unused-vars": ["error", {"ignoreRestSiblings": true}],
"@typescript-eslint/no-use-before-define": "error", "@typescript-eslint/no-use-before-define": "error",
"@typescript-eslint/no-useless-constructor": "error", "@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/prefer-includes": "error", "@typescript-eslint/prefer-includes": "error",

View File

@@ -9,8 +9,7 @@ Tooling tips: https://zulip.readthedocs.io/en/latest/tutorials/screenshot-and-gi
**Screenshots and screen captures:** **Screenshots and screen captures:**
<details> **Self-review checklist**
<summary>Self-review checklist</summary>
<!-- Prior to submitting a PR, follow our step-by-step guide to review your own code: <!-- Prior to submitting a PR, follow our step-by-step guide to review your own code:
https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#how-to-review-code --> https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#how-to-review-code -->
@@ -28,7 +27,7 @@ Communicate decisions, questions, and potential concerns.
- [ ] Calls out remaining decisions and concerns. - [ ] Calls out remaining decisions and concerns.
- [ ] Automated tests verify logic where appropriate. - [ ] Automated tests verify logic where appropriate.
Individual commits are ready for review (see [commit discipline](https://zulip.readthedocs.io/en/latest/contributing/commit-discipline.html)). Individual commits are ready for review (see [commit discipline](https://zulip.readthedocs.io/en/latest/contributing/version-control.html)).
- [ ] Each commit is a coherent idea. - [ ] Each commit is a coherent idea.
- [ ] Commit message(s) explain reasoning and motivation for changes. - [ ] Commit message(s) explain reasoning and motivation for changes.
@@ -40,4 +39,3 @@ Completed manual review and testing of the following:
- [ ] Strings and tooltips. - [ ] Strings and tooltips.
- [ ] End-to-end functionality of buttons, interactions and flows. - [ ] End-to-end functionality of buttons, interactions and flows.
- [ ] Corner cases, error conditions, and easily imagined bugs. - [ ] Corner cases, error conditions, and easily imagined bugs.
</details>

View File

@@ -47,7 +47,6 @@ jobs:
# the top explain how to build and upload these images. # the top explain how to build and upload these images.
# Ubuntu 20.04 ships with Python 3.8.10. # Ubuntu 20.04 ships with Python 3.8.10.
container: zulip/ci:focal container: zulip/ci:focal
steps: steps:
- name: Add required permissions - name: Add required permissions
run: | run: |
@@ -104,22 +103,11 @@ jobs:
path: /tmp/production-build path: /tmp/production-build
retention-days: 14 retention-days: 14
- name: Generate failure report string - name: Report status
id: failure_report_string if: failure()
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }} env:
run: tools/ci/generate-failure-message >> $GITHUB_OUTPUT ZULIP_BOT_KEY: ${{ secrets.ZULIP_BOT_KEY }}
run: tools/ci/send-failure-message
- name: Report status to CZO
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }}
uses: zulip/github-actions-zulip/send-message@v1
with:
api-key: ${{ secrets.ZULIP_BOT_KEY }}
email: "github-actions-bot@chat.zulip.org"
organization-url: "https://chat.zulip.org"
to: "automated testing"
topic: ${{ steps.failure_report_string.outputs.topic }}
type: "stream"
content: ${{ steps.failure_report_string.outputs.content }}
production_install: production_install:
# This job installs the server release tarball built above on a # This job installs the server release tarball built above on a
@@ -172,7 +160,7 @@ jobs:
chmod +x /tmp/production-pgroonga chmod +x /tmp/production-pgroonga
chmod +x /tmp/production-install chmod +x /tmp/production-install
chmod +x /tmp/production-verify chmod +x /tmp/production-verify
chmod +x /tmp/generate-failure-message chmod +x /tmp/send-failure-message
- name: Create cache directories - name: Create cache directories
run: | run: |
@@ -188,7 +176,9 @@ jobs:
restore-keys: v1-yarn-deps-${{ matrix.os }} restore-keys: v1-yarn-deps-${{ matrix.os }}
- name: Install production - name: Install production
run: sudo /tmp/production-install ${{ matrix.extra-args }} run: |
sudo service rabbitmq-server restart
sudo /tmp/production-install ${{ matrix.extra-args }}
- name: Verify install - name: Verify install
run: sudo /tmp/production-verify ${{ matrix.extra-args }} run: sudo /tmp/production-verify ${{ matrix.extra-args }}
@@ -209,22 +199,11 @@ jobs:
if: ${{ matrix.os == 'focal' }} if: ${{ matrix.os == 'focal' }}
run: sudo /tmp/production-verify ${{ matrix.extra-args }} run: sudo /tmp/production-verify ${{ matrix.extra-args }}
- name: Generate failure report string - name: Report status
id: failure_report_string if: failure()
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }} env:
run: tools/ci/generate-failure-message >> $GITHUB_OUTPUT ZULIP_BOT_KEY: ${{ secrets.ZULIP_BOT_KEY }}
run: /tmp/send-failure-message
- name: Report status to CZO
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }}
uses: zulip/github-actions-zulip/send-message@v1
with:
api-key: ${{ secrets.ZULIP_BOT_KEY }}
email: "github-actions-bot@chat.zulip.org"
organization-url: "https://chat.zulip.org"
to: "automated testing"
topic: ${{ steps.failure_report_string.outputs.topic }}
type: "stream"
content: ${{ steps.failure_report_string.outputs.content }}
production_upgrade: production_upgrade:
# The production upgrade job starts with a container with a # The production upgrade job starts with a container with a
@@ -248,9 +227,6 @@ jobs:
- docker_image: zulip/ci:bullseye-5.0 - docker_image: zulip/ci:bullseye-5.0
name: 5.0 Version Upgrade name: 5.0 Version Upgrade
os: bullseye os: bullseye
- docker_image: zulip/ci:bullseye-6.0
name: 6.0 Version Upgrade
os: bullseye
name: ${{ matrix.name }} name: ${{ matrix.name }}
container: container:
@@ -277,7 +253,7 @@ jobs:
# of the tarball uploaded by the upload artifact fix those. # of the tarball uploaded by the upload artifact fix those.
chmod +x /tmp/production-upgrade chmod +x /tmp/production-upgrade
chmod +x /tmp/production-verify chmod +x /tmp/production-verify
chmod +x /tmp/generate-failure-message chmod +x /tmp/send-failure-message
- name: Create cache directories - name: Create cache directories
run: | run: |
@@ -294,19 +270,8 @@ jobs:
# - name: Verify install # - name: Verify install
# run: sudo /tmp/production-verify # run: sudo /tmp/production-verify
- name: Generate failure report string - name: Report status
id: failure_report_string if: failure()
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }} env:
run: tools/ci/generate-failure-message >> $GITHUB_OUTPUT ZULIP_BOT_KEY: ${{ secrets.ZULIP_BOT_KEY }}
run: /tmp/send-failure-message
- name: Report status to CZO
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }}
uses: zulip/github-actions-zulip/send-message@v1
with:
api-key: ${{ secrets.ZULIP_BOT_KEY }}
email: "github-actions-bot@chat.zulip.org"
organization-url: "https://chat.zulip.org"
to: "automated testing"
topic: ${{ steps.failure_report_string.outputs.topic }}
type: "stream"
content: ${{ steps.failure_report_string.outputs.content }}

View File

@@ -150,7 +150,6 @@ jobs:
./tools/test-migrations ./tools/test-migrations
./tools/setup/optimize-svg --check ./tools/setup/optimize-svg --check
./tools/setup/generate_integration_bots_avatars.py --check-missing ./tools/setup/generate_integration_bots_avatars.py --check-missing
./tools/ci/check-executables
# Ban check-database-compatibility.py from transitively # Ban check-database-compatibility.py from transitively
# relying on static/generated, because it might not be # relying on static/generated, because it might not be
@@ -234,19 +233,8 @@ jobs:
- name: Check development database build - name: Check development database build
run: ./tools/ci/setup-backend run: ./tools/ci/setup-backend
- name: Generate failure report string - name: Report status
id: failure_report_string if: failure()
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }} env:
run: tools/ci/generate-failure-message >> $GITHUB_OUTPUT ZULIP_BOT_KEY: ${{ secrets.ZULIP_BOT_KEY }}
run: tools/ci/send-failure-message
- name: Report status to CZO
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }}
uses: zulip/github-actions-zulip/send-message@v1
with:
api-key: ${{ secrets.ZULIP_BOT_KEY }}
email: "github-actions-bot@chat.zulip.org"
organization-url: "https://chat.zulip.org"
to: "automated testing"
topic: ${{ steps.failure_report_string.outputs.topic }}
type: "stream"
content: ${{ steps.failure_report_string.outputs.content }}

1
.gitignore vendored
View File

@@ -33,7 +33,6 @@ package-lock.json
!/var/puppeteer/test_credentials.d.ts !/var/puppeteer/test_credentials.d.ts
/.dmypy.json /.dmypy.json
/.ruff_cache
# Generated i18n data # Generated i18n data
/locale/en /locale/en

View File

@@ -24,7 +24,6 @@ Anders Kaseorg <anders@zulip.com> <anders@zulipchat.com>
Anders Kaseorg <anders@zulip.com> <andersk@mit.edu> Anders Kaseorg <anders@zulip.com> <andersk@mit.edu>
Aryan Shridhar <aryanshridhar7@gmail.com> <53977614+aryanshridhar@users.noreply.github.com> Aryan Shridhar <aryanshridhar7@gmail.com> <53977614+aryanshridhar@users.noreply.github.com>
Aryan Shridhar <aryanshridhar7@gmail.com> Aryan Shridhar <aryanshridhar7@gmail.com>
aparna-bhatt <aparnabhatt2001@gmail.com> <86338542+aparna-bhatt@users.noreply.github.com>
Ashwat Kumar Singh <ashwat.kumarsingh.met20@itbhu.ac.in> Ashwat Kumar Singh <ashwat.kumarsingh.met20@itbhu.ac.in>
Austin Riba <austin@zulip.com> <austin@m51.io> Austin Riba <austin@zulip.com> <austin@m51.io>
BIKI DAS <bikid475@gmail.com> BIKI DAS <bikid475@gmail.com>
@@ -34,8 +33,6 @@ Brock Whittaker <brock@zulipchat.com> <brock@zulipchat.org>
Chris Bobbe <cbobbe@zulip.com> <cbobbe@zulipchat.com> Chris Bobbe <cbobbe@zulip.com> <cbobbe@zulipchat.com>
Chris Bobbe <cbobbe@zulip.com> <csbobbe@gmail.com> Chris Bobbe <cbobbe@zulip.com> <csbobbe@gmail.com>
Eeshan Garg <eeshan@zulip.com> <jerryguitarist@gmail.com> Eeshan Garg <eeshan@zulip.com> <jerryguitarist@gmail.com>
Eric Smith <erwsmith@gmail.com> <99841919+erwsmith@users.noreply.github.com>
Ganesh Pawar <pawarg256@gmail.com> <58626718+ganpa3@users.noreply.github.com>
Greg Price <greg@zulip.com> <gnprice@gmail.com> Greg Price <greg@zulip.com> <gnprice@gmail.com>
Greg Price <greg@zulip.com> <greg@zulipchat.com> Greg Price <greg@zulip.com> <greg@zulipchat.com>
Greg Price <greg@zulip.com> <price@mit.edu> Greg Price <greg@zulip.com> <price@mit.edu>
@@ -50,30 +47,21 @@ Kevin Mehall <km@kevinmehall.net> <kevin@humbughq.com>
Kevin Mehall <km@kevinmehall.net> <kevin@zulip.com> Kevin Mehall <km@kevinmehall.net> <kevin@zulip.com>
Kevin Scott <kevin.scott.98@gmail.com> Kevin Scott <kevin.scott.98@gmail.com>
Lauryn Menard <lauryn@zulip.com> <lauryn.menard@gmail.com> Lauryn Menard <lauryn@zulip.com> <lauryn.menard@gmail.com>
Lauryn Menard <lauryn@zulip.com> <63245456+laurynmm@users.noreply.github.com>
Mateusz Mandera <mateusz.mandera@zulip.com> <mateusz.mandera@protonmail.com> Mateusz Mandera <mateusz.mandera@zulip.com> <mateusz.mandera@protonmail.com>
Matt Keller <matt@zulip.com>
Matt Keller <matt@zulip.com> <m@cognusion.com>
m-e-l-u-h-a-n <purushottam.tiwari.cd.cse19@itbhu.ac.in> m-e-l-u-h-a-n <purushottam.tiwari.cd.cse19@itbhu.ac.in>
Noble Mittal <noblemittal@outlook.com> <62551163+beingnoble03@users.noreply.github.com>
Palash Raghuwanshi <singhpalash0@gmail.com> Palash Raghuwanshi <singhpalash0@gmail.com>
Parth <mittalparth22@gmail.com> Parth <mittalparth22@gmail.com>
Priyam Seth <sethpriyam1@gmail.com> <b19188@students.iitmandi.ac.in> Priyam Seth <sethpriyam1@gmail.com> <b19188@students.iitmandi.ac.in>
Ray Kraesig <rkraesig@zulip.com> <rkraesig@zulipchat.com> Ray Kraesig <rkraesig@zulip.com> <rkraesig@zulipchat.com>
Reid Barton <rwbarton@gmail.com> <rwbarton@humbughq.com> Reid Barton <rwbarton@gmail.com> <rwbarton@humbughq.com>
Rein Zustand (rht) <rhtbot@protonmail.com>
Rishi Gupta <rishig@zulipchat.com> <rishig+git@mit.edu> Rishi Gupta <rishig@zulipchat.com> <rishig+git@mit.edu>
Rishi Gupta <rishig@zulipchat.com> <rishig@kandralabs.com> Rishi Gupta <rishig@zulipchat.com> <rishig@kandralabs.com>
Rishi Gupta <rishig@zulipchat.com> <rishig@users.noreply.github.com> Rishi Gupta <rishig@zulipchat.com> <rishig@users.noreply.github.com>
Rishabh Maheshwari <b20063@students.iitmandi.ac.in> Rishabh Maheshwari <b20063@students.iitmandi.ac.in>
Rixant Rokaha <rixantrokaha@gmail.com>
Rixant Rokaha <rixantrokaha@gmail.com> <rishantrokaha@gmail.com>
Rixant Rokaha <rixantrokaha@gmail.com> <rrokaha@caldwell.edu>
Sayam Samal <samal.sayam@gmail.com> Sayam Samal <samal.sayam@gmail.com>
Scott Feeney <scott@oceanbase.org> <scott@humbughq.com> Scott Feeney <scott@oceanbase.org> <scott@humbughq.com>
Scott Feeney <scott@oceanbase.org> <scott@zulip.com> Scott Feeney <scott@oceanbase.org> <scott@zulip.com>
Shlok Patel <shlokcpatel2001@gmail.com> Shlok Patel <shlokcpatel2001@gmail.com>
Somesh Ranjan <somesh.ranjan.met20@itbhu.ac.in> <77766761+somesh202@users.noreply.github.com>
Steve Howell <showell@zulip.com> <showell30@yahoo.com> Steve Howell <showell@zulip.com> <showell30@yahoo.com>
Steve Howell <showell@zulip.com> <showell@yahoo.com> Steve Howell <showell@zulip.com> <showell@yahoo.com>
Steve Howell <showell@zulip.com> <showell@zulipchat.com> Steve Howell <showell@zulip.com> <showell@zulipchat.com>
@@ -91,12 +79,5 @@ Sahil Batra <sahil@zulip.com> <sahilbatra839@gmail.com>
Yash RE <33805964+YashRE42@users.noreply.github.com> <YashRE42@github.com> Yash RE <33805964+YashRE42@users.noreply.github.com> <YashRE42@github.com>
Yash RE <33805964+YashRE42@users.noreply.github.com> Yash RE <33805964+YashRE42@users.noreply.github.com>
Yogesh Sirsat <yogeshsirsat56@gmail.com> Yogesh Sirsat <yogeshsirsat56@gmail.com>
Yogesh Sirsat <yogeshsirsat56@gmail.com> <41695888+yogesh-sirsat@users.noreply.github.com>
Zeeshan Equbal <equbalzeeshan@gmail.com> <54993043+zee-bit@users.noreply.github.com> Zeeshan Equbal <equbalzeeshan@gmail.com> <54993043+zee-bit@users.noreply.github.com>
Zeeshan Equbal <equbalzeeshan@gmail.com> Zeeshan Equbal <equbalzeeshan@gmail.com>
Zev Benjamin <zev@zulip.com> <zev@dropbox.com>
Zev Benjamin <zev@zulip.com> <zev@humbughq.com>
Zev Benjamin <zev@zulip.com> <zev@mit.edu>
Zixuan James Li <p359101898@gmail.com>
Zixuan James Li <p359101898@gmail.com> <39874143+PIG208@users.noreply.github.com>
Zixuan James Li <p359101898@gmail.com> <359101898@qq.com>

View File

@@ -1,39 +1,32 @@
# Migrated from transifex-client format with `tx migrate`
#
# See https://developers.transifex.com/docs/using-the-client which hints at
# this format, but in general, the headings are in the format of:
#
# [o:<org>:p:<project>:r:<resource>]
[main] [main]
host = https://www.transifex.com host = https://www.transifex.com
lang_map = zh-Hans: zh_Hans, zh-Hant: zh_Hant lang_map = zh-Hans: zh_Hans, zh-Hant: zh_Hant
[o:zulip:p:zulip:r:djangopo] [zulip.djangopo]
file_filter = locale/<lang>/LC_MESSAGES/django.po file_filter = locale/<lang>/LC_MESSAGES/django.po
source_file = locale/en/LC_MESSAGES/django.po source_file = locale/en/LC_MESSAGES/django.po
source_lang = en source_lang = en
type = PO type = PO
[o:zulip:p:zulip:r:mobile] [zulip.translationsjson]
file_filter = locale/<lang>/translations.json
source_file = locale/en/translations.json
source_lang = en
type = KEYVALUEJSON
[zulip.mobile]
file_filter = locale/<lang>/mobile.json file_filter = locale/<lang>/mobile.json
source_file = locale/en/mobile.json source_file = locale/en/mobile.json
source_lang = en source_lang = en
type = KEYVALUEJSON type = KEYVALUEJSON
[o:zulip:p:zulip:r:translationsjson] [zulip-test.djangopo]
file_filter = locale/<lang>/translations.json
source_file = locale/en/translations.json
source_lang = en
type = KEYVALUEJSON
[o:zulip:p:zulip-test:r:djangopo]
file_filter = locale/<lang>/LC_MESSAGES/django.po file_filter = locale/<lang>/LC_MESSAGES/django.po
source_file = locale/en/LC_MESSAGES/django.po source_file = locale/en/LC_MESSAGES/django.po
source_lang = en source_lang = en
type = PO type = PO
[o:zulip:p:zulip-test:r:translationsjson] [zulip-test.translationsjson]
file_filter = locale/<lang>/translations.json file_filter = locale/<lang>/translations.json
source_file = locale/en/translations.json source_file = locale/en/translations.json
source_lang = en source_lang = en

View File

@@ -102,71 +102,3 @@ This Code of Conduct is adapted from the
under a under a
[Creative Commons BY-SA](https://creativecommons.org/licenses/by-sa/4.0/) [Creative Commons BY-SA](https://creativecommons.org/licenses/by-sa/4.0/)
license. license.
## Moderating the Zulip community
Anyone can help moderate the Zulip community by helping make sure that folks are
aware of the [community guidelines](https://zulip.com/development-community/)
and this Code of Conduct, and that we maintain a positive and respectful
atmosphere.
Here are some guidelines for you how can help:
- Be friendly! Welcoming folks, thanking them for their feedback, ideas and effort,
and just trying to keep the atmosphere warm make the whole community function
more smoothly. New participants who feel accepted, listened to and respected
are likely to treat others the same way.
- Be familiar with the [community
guidelines](https://zulip.com/development-community/), and cite them liberally
when a user violates them. Be polite but firm. Some examples:
- @user please note that there is no need to @-mention @\_**Tim Abbott** when
you ask a question. As noted in the [guidelines for this
community](https://zulip.com/development-community/):
> Use @-mentions sparingly… there is generally no need to @-mention a
> core contributor unless you need their timely attention.
- @user, please keep in mind the following [community
guideline](https://zulip.com/development-community/):
> Dont ask the same question in multiple places. Moderators read every
> public stream, and make sure every question gets a reply.
Ive gone ahead and moved the other copy of this message to this thread.
- If asked a question in a PM that is better discussed in a public stream:
> Hi @user! Please start by reviewing
> https://zulip.com/development-community/#community-norms to learn how to
> get help in this community.
- Users sometimes think chat.zulip.org is a testing instance. When this happens,
kindly direct them to use the **#test here** stream.
- If you see a message thats posted in the wrong place, go ahead and move it if
you have permissions to do so, even if you dont plan to respond to it.
Leaving the “Send automated notice to new topic” option enabled helps make it
clear what happened to the person who sent the message.
If you are responding to a message that's been moved, mention the user in your
reply, so that the mention serves as a notification of the new location for
their conversation.
- If a user is posting spam, please report it to an administrator. They will:
- Change the user's name to `<name> (spammer)` and deactivate them.
- Delete any spam messages they posted in public streams.
- We care very much about maintaining a respectful tone in our community. If you
see someone being mean or rude, point out that their tone is inappropriate,
and ask them to communicate their perspective in a respectful way in the
future. If you dont feel comfortable doing so yourself, feel free to ask a
member of Zulip's core team to take care of the situation.
- Try to assume the best intentions from others (given the range of
possibilities presented by their visible behavior), and stick with a friendly
and positive tone even when someones behavior is poor or disrespectful.
Everyone has bad days and stressful situations that can result in them
behaving not their best, and while we should be firm about our community
rules, we should also enforce them with kindness.

View File

@@ -2,35 +2,16 @@
Welcome to the Zulip community! Welcome to the Zulip community!
## Zulip development community ## Community
The primary communication forum for the Zulip community is the Zulip The
server hosted at [chat.zulip.org](https://chat.zulip.org/): [Zulip community server](https://zulip.com/development-community/)
is the primary communication forum for the Zulip community. It is a good
- **Users** and **administrators** of Zulip organizations stop by to place to start whether you have a question, are a new contributor, are a new
ask questions, offer feedback, and participate in product design user, or anything else. Please review our
discussions. [community norms](https://zulip.com/development-community/#community-norms)
- **Contributors to the project**, including the **core Zulip before posting. The Zulip community is also governed by a
development team**, discuss ongoing and future projects, brainstorm [code of conduct](https://zulip.readthedocs.io/en/latest/code-of-conduct.html).
ideas, and generally help each other out.
Everyone is welcome to [sign up](https://chat.zulip.org/) and
participate — we love hearing from our users! Public streams in the
community receive thousands of messages a week. We recommend signing
up using the special invite links for
[users](https://chat.zulip.org/join/t5crtoe62bpcxyisiyglmtvb/),
[self-hosters](https://chat.zulip.org/join/wnhv3jzm6afa4raenedanfno/)
and
[contributors](https://chat.zulip.org/join/npzwak7vpmaknrhxthna3c7p/)
to get a curated list of initial stream subscriptions.
To learn how to get started participating in the community, including [community
norms](https://zulip.com/development-community/#community-norms) and [where to
post](https://zulip.com/development-community/#where-do-i-send-my-message),
check out our [Zulip development community
guide](https://zulip.com/development-community/). The Zulip community is
governed by a [code of
conduct](https://zulip.readthedocs.io/en/latest/code-of-conduct.html).
## Ways to contribute ## Ways to contribute
@@ -76,13 +57,12 @@ to help.
- First, make an account on the - First, make an account on the
[Zulip community server](https://zulip.com/development-community/), [Zulip community server](https://zulip.com/development-community/),
paying special attention to the paying special attention to the community norms. If you'd like, introduce
[community norms](https://zulip.com/development-community/#community-norms). yourself in
If you'd like, introduce yourself in
[#new members](https://chat.zulip.org/#narrow/stream/95-new-members), using [#new members](https://chat.zulip.org/#narrow/stream/95-new-members), using
your name as the topic. Bonus: tell us about your first impressions of your name as the topic. Bonus: tell us about your first impressions of
Zulip, and anything that felt confusing/broken or interesting/helpful as you Zulip, and anything that felt confusing/broken as you started using the
started using the product. product.
- Read [What makes a great Zulip contributor](#what-makes-a-great-zulip-contributor). - Read [What makes a great Zulip contributor](#what-makes-a-great-zulip-contributor).
- [Install the development environment](https://zulip.readthedocs.io/en/latest/development/overview.html), - [Install the development environment](https://zulip.readthedocs.io/en/latest/development/overview.html),
getting help in getting help in
@@ -147,6 +127,14 @@ Note that you are _not_ claiming an issue while you are iterating through steps
1-4. _Before you claim an issue_, you should be confident that you will be able to 1-4. _Before you claim an issue_, you should be confident that you will be able to
tackle it effectively. tackle it effectively.
If the lists of issues are overwhelming, you can post in
[#new members](https://chat.zulip.org/#narrow/stream/95-new-members) with a
bit about your background and interests, and we'll help you out. The most
important thing to say is whether you're looking for a backend (Python),
frontend (JavaScript and TypeScript), mobile (React Native), desktop (Electron),
documentation (English) or visual design (JavaScript/TypeScript + CSS) issue, and a
bit about your programming experience and available time.
Additional tips for the [main server and web app Additional tips for the [main server and web app
repository](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22): repository](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22):
@@ -333,21 +321,10 @@ labels.
have a new feature you'd like to add, you can start a conversation [in our have a new feature you'd like to add, you can start a conversation [in our
development community](https://zulip.com/development-community/#where-do-i-send-my-message) development community](https://zulip.com/development-community/#where-do-i-send-my-message)
explaining the feature idea and the problem that you're hoping to solve. explaining the feature idea and the problem that you're hoping to solve.
- **I'm waiting for the next round of review on my PR. Can I pick up
another issue in the meantime?** Someone's first Zulip PR often
requires quite a bit of iteration, so please [make sure your pull
request is reviewable][reviewable-pull-requests] and go through at
least one round of feedback from others before picking up a second
issue. After that, sure! If
[Zulipbot](https://github.com/zulip/zulipbot) does not allow you to
claim an issue, you can post a comment describing the status of your
other work on the issue you're interested in, and asking for the
issue to be assigned to you. Note that addressing feedback on
in-progress PRs should always take priority over starting a new PR.
- **I think my PR is done, but it hasn't been merged yet. What's going on?** - **I think my PR is done, but it hasn't been merged yet. What's going on?**
1. **Double-check that you have addressed all the feedback**, including any comments 1. **Double-check that you have addressed all the feedback**, including any comments
on [Git commit on [Git commit
discipline](https://zulip.readthedocs.io/en/latest/contributing/commit-discipline.html). discipline](https://zulip.readthedocs.io/en/latest/contributing/version-control.html#commit-discipline).
2. If all the feedback has been addressed, did you [leave a 2. If all the feedback has been addressed, did you [leave a
comment](#how-to-help-move-the-review-process-forward) comment](#how-to-help-move-the-review-process-forward)
explaining that you have done so and **requesting another review**? If not, explaining that you have done so and **requesting another review**? If not,
@@ -365,8 +342,6 @@ labels.
occasionally take a few weeks for a PR in the final stages of the review occasionally take a few weeks for a PR in the final stages of the review
process to be merged. process to be merged.
[reviewable-pull-requests]: https://zulip.readthedocs.io/en/latest/contributing/reviewable-prs.html
## What makes a great Zulip contributor? ## What makes a great Zulip contributor?
Zulip has a lot of experience working with new contributors. In our Zulip has a lot of experience working with new contributors. In our
@@ -378,7 +353,7 @@ experience, these are the best predictors of success:
you got stuck. Post tracebacks or other error messages if appropriate. For you got stuck. Post tracebacks or other error messages if appropriate. For
more advice, check out [our guide][great-questions]! more advice, check out [our guide][great-questions]!
- Learning and practicing - Learning and practicing
[Git commit discipline](https://zulip.readthedocs.io/en/latest/contributing/commit-discipline.html). [Git commit discipline](https://zulip.readthedocs.io/en/latest/contributing/version-control.html#commit-discipline).
- Submitting carefully tested code. See our [detailed guide on how to review - Submitting carefully tested code. See our [detailed guide on how to review
code](https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#how-to-review-code) code](https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#how-to-review-code)
(yours or someone else's). (yours or someone else's).

View File

@@ -17,7 +17,6 @@ Come find us on the [development community chat](https://zulip.com/development-c
[![GitHub Actions build status](https://github.com/zulip/zulip/actions/workflows/zulip-ci.yml/badge.svg)](https://github.com/zulip/zulip/actions/workflows/zulip-ci.yml?query=branch%3Amain) [![GitHub Actions build status](https://github.com/zulip/zulip/actions/workflows/zulip-ci.yml/badge.svg)](https://github.com/zulip/zulip/actions/workflows/zulip-ci.yml?query=branch%3Amain)
[![coverage status](https://img.shields.io/codecov/c/github/zulip/zulip/main.svg)](https://codecov.io/gh/zulip/zulip) [![coverage status](https://img.shields.io/codecov/c/github/zulip/zulip/main.svg)](https://codecov.io/gh/zulip/zulip)
[![Mypy coverage](https://img.shields.io/badge/mypy-100%25-green.svg)][mypy-coverage] [![Mypy coverage](https://img.shields.io/badge/mypy-100%25-green.svg)][mypy-coverage]
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v0.json)](https://github.com/charliermarsh/ruff)
[![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
[![GitHub release](https://img.shields.io/github/release/zulip/zulip.svg)](https://github.com/zulip/zulip/releases/latest) [![GitHub release](https://img.shields.io/github/release/zulip/zulip.svg)](https://github.com/zulip/zulip/releases/latest)

View File

@@ -8,7 +8,7 @@ from django.utils.timezone import now as timezone_now
from analytics.lib.counts import COUNT_STATS, CountStat from analytics.lib.counts import COUNT_STATS, CountStat
from analytics.models import installation_epoch from analytics.models import installation_epoch
from zerver.lib.timestamp import TimeZoneNotUTCError, floor_to_day, floor_to_hour, verify_UTC from zerver.lib.timestamp import TimeZoneNotUTCException, floor_to_day, floor_to_hour, verify_UTC
from zerver.models import Realm from zerver.models import Realm
states = { states = {
@@ -48,7 +48,7 @@ class Command(BaseCommand):
last_fill = installation_epoch() last_fill = installation_epoch()
try: try:
verify_UTC(last_fill) verify_UTC(last_fill)
except TimeZoneNotUTCError: except TimeZoneNotUTCException:
return {"status": 2, "message": f"FillState not in UTC for {property}"} return {"status": 2, "message": f"FillState not in UTC for {property}"}
if stat.frequency == CountStat.DAY: if stat.frequency == CountStat.DAY:

View File

@@ -92,16 +92,6 @@ class Command(BaseCommand):
) )
do_change_user_role(shylock, UserProfile.ROLE_REALM_OWNER, acting_user=None) do_change_user_role(shylock, UserProfile.ROLE_REALM_OWNER, acting_user=None)
# Create guest user for set_guest_users_statistic.
create_user(
"bassanio@analytics.ds",
"Bassanio",
realm,
full_name="Bassanio",
role=UserProfile.ROLE_GUEST,
force_date_joined=installation_time,
)
administrators_user_group = UserGroup.objects.get( administrators_user_group = UserGroup.objects.get(
name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=realm, is_system_group=True name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=realm, is_system_group=True
) )

View File

@@ -3,6 +3,7 @@ from unittest import mock
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import queries_captured
from zerver.models import Client, UserActivity, UserProfile, flush_per_request_caches from zerver.models import Client, UserActivity, UserProfile, flush_per_request_caches
@@ -32,17 +33,23 @@ class ActivityTest(ZulipTestCase):
user_profile.save(update_fields=["is_staff"]) user_profile.save(update_fields=["is_staff"])
flush_per_request_caches() flush_per_request_caches()
with self.assert_database_query_count(18): with queries_captured() as queries:
result = self.client_get("/activity") result = self.client_get("/activity")
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
self.assert_length(queries, 19)
flush_per_request_caches() flush_per_request_caches()
with self.assert_database_query_count(8): with queries_captured() as queries:
result = self.client_get("/realm_activity/zulip/") result = self.client_get("/realm_activity/zulip/")
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
self.assert_length(queries, 8)
iago = self.example_user("iago") iago = self.example_user("iago")
flush_per_request_caches() flush_per_request_caches()
with self.assert_database_query_count(5): with queries_captured() as queries:
result = self.client_get(f"/user_activity/{iago.id}/") result = self.client_get(f"/user_activity/{iago.id}/")
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
self.assert_length(queries, 5)

View File

@@ -53,7 +53,7 @@ from zerver.actions.users import do_deactivate_user
from zerver.lib.create_user import create_user from zerver.lib.create_user import create_user
from zerver.lib.exceptions import InvitationError from zerver.lib.exceptions import InvitationError
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.timestamp import TimeZoneNotUTCError, floor_to_day from zerver.lib.timestamp import TimeZoneNotUTCException, floor_to_day
from zerver.lib.topic import DB_TOPIC_NAME from zerver.lib.topic import DB_TOPIC_NAME
from zerver.lib.utils import assert_is_not_none from zerver.lib.utils import assert_is_not_none
from zerver.models import ( from zerver.models import (
@@ -290,7 +290,7 @@ class TestProcessCountStat(AnalyticsTestCase):
stat = self.make_dummy_count_stat("test stat") stat = self.make_dummy_count_stat("test stat")
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
process_count_stat(stat, installation_epoch() + 65 * self.MINUTE) process_count_stat(stat, installation_epoch() + 65 * self.MINUTE)
with self.assertRaises(TimeZoneNotUTCError): with self.assertRaises(TimeZoneNotUTCException):
process_count_stat(stat, installation_epoch().replace(tzinfo=None)) process_count_stat(stat, installation_epoch().replace(tzinfo=None))
# This tests the LoggingCountStat branch of the code in do_delete_counts_at_hour. # This tests the LoggingCountStat branch of the code in do_delete_counts_at_hour.

View File

@@ -3,16 +3,14 @@ import sys
from datetime import datetime from datetime import datetime
from html import escape from html import escape
from typing import Any, Collection, Dict, List, Optional, Sequence from typing import Any, Collection, Dict, List, Optional, Sequence
from urllib.parse import urlencode
from django.conf import settings from django.conf import settings
from django.db.backends.utils import CursorWrapper from django.db.backends.utils import CursorWrapper
from django.template import loader from django.template import loader
from django.urls import reverse from django.urls import reverse
from markupsafe import Markup from markupsafe import Markup as mark_safe
from zerver.lib.url_encoding import append_url_query_string from zerver.models import UserActivity
from zerver.models import UserActivity, get_realm
if sys.version_info < (3, 9): # nocoverage if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo from backports import zoneinfo
@@ -48,7 +46,7 @@ def make_table(
def dictfetchall(cursor: CursorWrapper) -> List[Dict[str, Any]]: def dictfetchall(cursor: CursorWrapper) -> List[Dict[str, Any]]:
"""Returns all rows from a cursor as a dict""" "Returns all rows from a cursor as a dict"
desc = cursor.description desc = cursor.description
return [dict(zip((col[0] for col in desc), row)) for row in cursor.fetchall()] return [dict(zip((col[0] for col in desc), row)) for row in cursor.fetchall()]
@@ -60,50 +58,36 @@ def format_date_for_activity_reports(date: Optional[datetime]) -> str:
return "" return ""
def user_activity_link(email: str, user_profile_id: int) -> Markup: def user_activity_link(email: str, user_profile_id: int) -> mark_safe:
from analytics.views.user_activity import get_user_activity from analytics.views.user_activity import get_user_activity
url = reverse(get_user_activity, kwargs=dict(user_profile_id=user_profile_id)) url = reverse(get_user_activity, kwargs=dict(user_profile_id=user_profile_id))
email_link = f'<a href="{escape(url)}">{escape(email)}</a>' email_link = f'<a href="{escape(url)}">{escape(email)}</a>'
return Markup(email_link) return mark_safe(email_link)
def realm_activity_link(realm_str: str) -> Markup: def realm_activity_link(realm_str: str) -> mark_safe:
from analytics.views.realm_activity import get_realm_activity from analytics.views.realm_activity import get_realm_activity
url = reverse(get_realm_activity, kwargs=dict(realm_str=realm_str)) url = reverse(get_realm_activity, kwargs=dict(realm_str=realm_str))
realm_link = f'<a href="{escape(url)}">{escape(realm_str)}</a>' realm_link = f'<a href="{escape(url)}">{escape(realm_str)}</a>'
return Markup(realm_link) return mark_safe(realm_link)
def realm_stats_link(realm_str: str) -> Markup: def realm_stats_link(realm_str: str) -> mark_safe:
from analytics.views.stats import stats_for_realm from analytics.views.stats import stats_for_realm
url = reverse(stats_for_realm, kwargs=dict(realm_str=realm_str)) url = reverse(stats_for_realm, kwargs=dict(realm_str=realm_str))
stats_link = f'<a href="{escape(url)}"><i class="fa fa-pie-chart"></i></a>' stats_link = f'<a href="{escape(url)}"><i class="fa fa-pie-chart"></i>{escape(realm_str)}</a>'
return Markup(stats_link) return mark_safe(stats_link)
def realm_support_link(realm_str: str) -> Markup: def remote_installation_stats_link(server_id: int, hostname: str) -> mark_safe:
support_url = reverse("support")
query = urlencode({"q": realm_str})
url = append_url_query_string(support_url, query)
support_link = f'<a href="{escape(url)}">{escape(realm_str)}</a>'
return Markup(support_link)
def realm_url_link(realm_str: str) -> Markup:
url = get_realm(realm_str).uri
realm_link = f'<a href="{escape(url)}"><i class="fa fa-home"></i></a>'
return Markup(realm_link)
def remote_installation_stats_link(server_id: int, hostname: str) -> Markup:
from analytics.views.stats import stats_for_remote_installation from analytics.views.stats import stats_for_remote_installation
url = reverse(stats_for_remote_installation, kwargs=dict(remote_server_id=server_id)) url = reverse(stats_for_remote_installation, kwargs=dict(remote_server_id=server_id))
stats_link = f'<a href="{escape(url)}"><i class="fa fa-pie-chart"></i>{escape(hostname)}</a>' stats_link = f'<a href="{escape(url)}"><i class="fa fa-pie-chart"></i>{escape(hostname)}</a>'
return Markup(stats_link) return mark_safe(stats_link)
def get_user_activity_summary(records: Collection[UserActivity]) -> Dict[str, Any]: def get_user_activity_summary(records: Collection[UserActivity]) -> Dict[str, Any]:

View File

@@ -10,7 +10,7 @@ from django.http import HttpRequest, HttpResponse
from django.shortcuts import render from django.shortcuts import render
from django.template import loader from django.template import loader
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from markupsafe import Markup from markupsafe import Markup as mark_safe
from psycopg2.sql import SQL, Composable, Literal from psycopg2.sql import SQL, Composable, Literal
from analytics.lib.counts import COUNT_STATS from analytics.lib.counts import COUNT_STATS
@@ -20,15 +20,13 @@ from analytics.views.activity_common import (
make_table, make_table,
realm_activity_link, realm_activity_link,
realm_stats_link, realm_stats_link,
realm_support_link,
realm_url_link,
remote_installation_stats_link, remote_installation_stats_link,
) )
from analytics.views.support import get_plan_name from analytics.views.support import get_plan_name
from zerver.decorator import require_server_admin from zerver.decorator import require_server_admin
from zerver.lib.request import has_request_variables from zerver.lib.request import has_request_variables
from zerver.lib.timestamp import timestamp_to_datetime from zerver.lib.timestamp import timestamp_to_datetime
from zerver.models import Realm, UserActivityInterval, get_org_type_display_name from zerver.models import Realm, UserActivityInterval, UserProfile, get_org_type_display_name
if settings.BILLING_ENABLED: if settings.BILLING_ENABLED:
from corporate.lib.stripe import ( from corporate.lib.stripe import (
@@ -189,10 +187,19 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
rows = dictfetchall(cursor) rows = dictfetchall(cursor)
cursor.close() cursor.close()
# Fetch all the realm administrator users
realm_owners: Dict[str, List[str]] = defaultdict(list)
for up in UserProfile.objects.select_related("realm").filter(
role=UserProfile.ROLE_REALM_OWNER,
is_active=True,
):
realm_owners[up.realm.string_id].append(up.delivery_email)
for row in rows: for row in rows:
row["date_created_day"] = row["date_created"].strftime("%Y-%m-%d") row["date_created_day"] = row["date_created"].strftime("%Y-%m-%d")
row["age_days"] = int((now - row["date_created"]).total_seconds() / 86400) row["age_days"] = int((now - row["date_created"]).total_seconds() / 86400)
row["is_new"] = row["age_days"] < 12 * 7 row["is_new"] = row["age_days"] < 12 * 7
row["realm_owner_emails"] = ", ".join(realm_owners[row["string_id"]])
# get messages sent per day # get messages sent per day
counts = get_realm_day_counts() counts = get_realm_day_counts()
@@ -248,9 +255,7 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
# formatting # formatting
for row in rows: for row in rows:
row["realm_url"] = realm_url_link(row["string_id"])
row["stats_link"] = realm_stats_link(row["string_id"]) row["stats_link"] = realm_stats_link(row["string_id"])
row["support_link"] = realm_support_link(row["string_id"])
row["string_id"] = realm_activity_link(row["string_id"]) row["string_id"] = realm_activity_link(row["string_id"])
# Count active sites # Count active sites
@@ -276,10 +281,9 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
org_type_string="", org_type_string="",
effective_rate="", effective_rate="",
arr=total_arr, arr=total_arr,
realm_url="",
stats_link="", stats_link="",
support_link="",
date_created_day="", date_created_day="",
realm_owner_emails="",
dau_count=total_dau_count, dau_count=total_dau_count,
user_profile_count=total_user_profile_count, user_profile_count=total_user_profile_count,
bot_count=total_bot_count, bot_count=total_bot_count,
@@ -294,14 +298,14 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
dict( dict(
rows=rows, rows=rows,
num_active_sites=num_active_sites, num_active_sites=num_active_sites,
utctime=now.strftime("%Y-%m-%d %H:%M %Z"), utctime=now.strftime("%Y-%m-%d %H:%MZ"),
billing_enabled=settings.BILLING_ENABLED, billing_enabled=settings.BILLING_ENABLED,
), ),
) )
return content return content
def user_activity_intervals() -> Tuple[Markup, Dict[str, float]]: def user_activity_intervals() -> Tuple[mark_safe, Dict[str, float]]:
day_end = timestamp_to_datetime(time.time()) day_end = timestamp_to_datetime(time.time())
day_start = day_end - timedelta(hours=24) day_start = day_end - timedelta(hours=24)
@@ -353,7 +357,7 @@ def user_activity_intervals() -> Tuple[Markup, Dict[str, float]]:
output += f"\nTotal duration: {total_duration}\n" output += f"\nTotal duration: {total_duration}\n"
output += f"\nTotal duration in minutes: {total_duration.total_seconds() / 60.}\n" output += f"\nTotal duration in minutes: {total_duration.total_seconds() / 60.}\n"
output += f"Total duration amortized to a month: {total_duration.total_seconds() * 30. / 60.}" output += f"Total duration amortized to a month: {total_duration.total_seconds() * 30. / 60.}"
content = Markup("<pre>" + output + "</pre>") content = mark_safe("<pre>" + output + "</pre>")
return content, realm_minutes return content, realm_minutes
@@ -368,7 +372,7 @@ def ad_hoc_queries() -> List[Dict[str, str]]:
cursor.close() cursor.close()
def fix_rows( def fix_rows(
i: int, fixup_func: Union[Callable[[str], Markup], Callable[[datetime], str]] i: int, fixup_func: Union[Callable[[str], mark_safe], Callable[[datetime], str]]
) -> None: ) -> None:
for row in rows: for row in rows:
row[i] = fixup_func(row[i]) row[i] = fixup_func(row[i])

View File

@@ -13,7 +13,6 @@ from analytics.views.activity_common import (
format_date_for_activity_reports, format_date_for_activity_reports,
get_user_activity_summary, get_user_activity_summary,
make_table, make_table,
realm_stats_link,
user_activity_link, user_activity_link,
) )
from zerver.decorator import require_server_admin from zerver.decorator import require_server_admin
@@ -253,10 +252,8 @@ def get_realm_activity(request: HttpRequest, realm_str: str) -> HttpResponse:
data += [(page_title, content)] data += [(page_title, content)]
title = realm_str title = realm_str
realm_stats = realm_stats_link(realm_str)
return render( return render(
request, request,
"analytics/activity.html", "analytics/activity.html",
context=dict(data=data, realm_stats_link=realm_stats, title=title), context=dict(data=data, realm_link=None, title=title),
) )

View File

@@ -49,36 +49,17 @@ def is_analytics_ready(realm: Realm) -> bool:
def render_stats( def render_stats(
request: HttpRequest, request: HttpRequest,
data_url_suffix: str, data_url_suffix: str,
realm: Optional[Realm], target_name: str,
*,
title: Optional[str] = None,
for_installation: bool = False, for_installation: bool = False,
remote: bool = False, remote: bool = False,
analytics_ready: bool = True, analytics_ready: bool = True,
) -> HttpResponse: ) -> HttpResponse:
assert request.user.is_authenticated assert request.user.is_authenticated
if realm is not None:
# Same query to get guest user count as in get_seat_count in corporate/lib/stripe.py.
guest_users = UserProfile.objects.filter(
realm=realm, is_active=True, is_bot=False, role=UserProfile.ROLE_GUEST
).count()
space_used = realm.currently_used_upload_space_bytes()
if title:
pass
else:
title = realm.name or realm.string_id
else:
assert title
guest_users = None
space_used = None
page_params = dict( page_params = dict(
data_url_suffix=data_url_suffix, data_url_suffix=data_url_suffix,
for_installation=for_installation, for_installation=for_installation,
remote=remote, remote=remote,
upload_space_used=space_used, upload_space_used=request.user.realm.currently_used_upload_space_bytes(),
guest_users=guest_users,
) )
request_language = get_and_set_request_language( request_language = get_and_set_request_language(
@@ -93,9 +74,7 @@ def render_stats(
request, request,
"analytics/stats.html", "analytics/stats.html",
context=dict( context=dict(
target_name=title, target_name=target_name, page_params=page_params, analytics_ready=analytics_ready
page_params=page_params,
analytics_ready=analytics_ready,
), ),
) )
@@ -108,7 +87,9 @@ def stats(request: HttpRequest) -> HttpResponse:
# TODO: Make @zulip_login_required pass the UserProfile so we # TODO: Make @zulip_login_required pass the UserProfile so we
# can use @require_member_or_admin # can use @require_member_or_admin
raise JsonableError(_("Not allowed for guest users")) raise JsonableError(_("Not allowed for guest users"))
return render_stats(request, "", realm, analytics_ready=is_analytics_ready(realm)) return render_stats(
request, "", realm.name or realm.string_id, analytics_ready=is_analytics_ready(realm)
)
@require_server_admin @require_server_admin
@@ -122,7 +103,7 @@ def stats_for_realm(request: HttpRequest, realm_str: str) -> HttpResponse:
return render_stats( return render_stats(
request, request,
f"/realm/{realm_str}", f"/realm/{realm_str}",
realm, realm.name or realm.string_id,
analytics_ready=is_analytics_ready(realm), analytics_ready=is_analytics_ready(realm),
) )
@@ -137,8 +118,7 @@ def stats_for_remote_realm(
return render_stats( return render_stats(
request, request,
f"/remote/{server.id}/realm/{remote_realm_id}", f"/remote/{server.id}/realm/{remote_realm_id}",
None, f"Realm {remote_realm_id} on server {server.hostname}",
title=f"Realm {remote_realm_id} on server {server.hostname}",
) )
@@ -179,8 +159,7 @@ def get_chart_data_for_remote_realm(
@require_server_admin @require_server_admin
def stats_for_installation(request: HttpRequest) -> HttpResponse: def stats_for_installation(request: HttpRequest) -> HttpResponse:
assert request.user.is_authenticated return render_stats(request, "/installation", "installation", True)
return render_stats(request, "/installation", None, title="installation", for_installation=True)
@require_server_admin @require_server_admin
@@ -190,10 +169,9 @@ def stats_for_remote_installation(request: HttpRequest, remote_server_id: int) -
return render_stats( return render_stats(
request, request,
f"/remote/{server.id}/installation", f"/remote/{server.id}/installation",
None, f"remote installation {server.hostname}",
title=f"remote installation {server.hostname}", True,
for_installation=True, True,
remote=True,
) )

View File

@@ -14,7 +14,7 @@ module.exports = {
[ [
"@babel/preset-env", "@babel/preset-env",
{ {
corejs: "3.27", corejs: "3.25",
shippedProposals: true, shippedProposals: true,
useBuiltIns: "usage", useBuiltIns: "usage",
}, },

View File

@@ -29,7 +29,7 @@ from zerver.models import (
) )
class ConfirmationKeyError(Exception): class ConfirmationKeyException(Exception):
WRONG_LENGTH = 1 WRONG_LENGTH = 1
EXPIRED = 2 EXPIRED = 2
DOES_NOT_EXIST = 3 DOES_NOT_EXIST = 3
@@ -40,11 +40,11 @@ class ConfirmationKeyError(Exception):
def render_confirmation_key_error( def render_confirmation_key_error(
request: HttpRequest, exception: ConfirmationKeyError request: HttpRequest, exception: ConfirmationKeyException
) -> HttpResponse: ) -> HttpResponse:
if exception.error_type == ConfirmationKeyError.WRONG_LENGTH: if exception.error_type == ConfirmationKeyException.WRONG_LENGTH:
return render(request, "confirmation/link_malformed.html", status=404) return render(request, "confirmation/link_malformed.html", status=404)
if exception.error_type == ConfirmationKeyError.EXPIRED: if exception.error_type == ConfirmationKeyException.EXPIRED:
return render(request, "confirmation/link_expired.html", status=404) return render(request, "confirmation/link_expired.html", status=404)
return render(request, "confirmation/link_does_not_exist.html", status=404) return render(request, "confirmation/link_does_not_exist.html", status=404)
@@ -77,16 +77,16 @@ def get_object_from_key(
# Confirmation keys used to be 40 characters # Confirmation keys used to be 40 characters
if len(confirmation_key) not in (24, 40): if len(confirmation_key) not in (24, 40):
raise ConfirmationKeyError(ConfirmationKeyError.WRONG_LENGTH) raise ConfirmationKeyException(ConfirmationKeyException.WRONG_LENGTH)
try: try:
confirmation = Confirmation.objects.get( confirmation = Confirmation.objects.get(
confirmation_key=confirmation_key, type__in=confirmation_types confirmation_key=confirmation_key, type__in=confirmation_types
) )
except Confirmation.DoesNotExist: except Confirmation.DoesNotExist:
raise ConfirmationKeyError(ConfirmationKeyError.DOES_NOT_EXIST) raise ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST)
if confirmation.expiry_date is not None and timezone_now() > confirmation.expiry_date: if confirmation.expiry_date is not None and timezone_now() > confirmation.expiry_date:
raise ConfirmationKeyError(ConfirmationKeyError.EXPIRED) raise ConfirmationKeyException(ConfirmationKeyException.EXPIRED)
obj = confirmation.content_object obj = confirmation.content_object
assert obj is not None assert obj is not None
@@ -96,10 +96,10 @@ def get_object_from_key(
if hasattr(obj, "status") and obj.status in [used_value, revoked_value]: if hasattr(obj, "status") and obj.status in [used_value, revoked_value]:
# Confirmations where the object has the status attribute are one-time use # Confirmations where the object has the status attribute are one-time use
# and are marked after being used (or revoked). # and are marked after being used (or revoked).
raise ConfirmationKeyError(ConfirmationKeyError.EXPIRED) raise ConfirmationKeyException(ConfirmationKeyException.EXPIRED)
if mark_object_used: if mark_object_used:
# MultiuseInvite objects do not use the STATUS_USED status, since they are # MultiuseInvite objects have no status field, since they are
# intended to be used more than once. # intended to be used more than once.
assert confirmation.type != Confirmation.MULTIUSE_INVITE assert confirmation.type != Confirmation.MULTIUSE_INVITE
assert hasattr(obj, "status") assert hasattr(obj, "status")
@@ -240,10 +240,10 @@ def validate_key(creation_key: Optional[str]) -> Optional["RealmCreationKey"]:
try: try:
key_record = RealmCreationKey.objects.get(creation_key=creation_key) key_record = RealmCreationKey.objects.get(creation_key=creation_key)
except RealmCreationKey.DoesNotExist: except RealmCreationKey.DoesNotExist:
raise RealmCreationKey.InvalidError() raise RealmCreationKey.Invalid()
time_elapsed = timezone_now() - key_record.date_created time_elapsed = timezone_now() - key_record.date_created
if time_elapsed.total_seconds() > settings.REALM_CREATION_LINK_VALIDITY_DAYS * 24 * 3600: if time_elapsed.total_seconds() > settings.REALM_CREATION_LINK_VALIDITY_DAYS * 24 * 3600:
raise RealmCreationKey.InvalidError() raise RealmCreationKey.Invalid()
return key_record return key_record
@@ -266,5 +266,5 @@ class RealmCreationKey(models.Model):
# is theirs, and skip sending mail to it to confirm that. # is theirs, and skip sending mail to it to confirm that.
presume_email_valid = models.BooleanField(default=False) presume_email_valid = models.BooleanField(default=False)
class InvalidError(Exception): class Invalid(Exception):
pass pass

View File

@@ -69,8 +69,6 @@ def get_seat_count(
.exclude(role=UserProfile.ROLE_GUEST) .exclude(role=UserProfile.ROLE_GUEST)
.count() .count()
) + extra_non_guests_count ) + extra_non_guests_count
# This guest count calculation should match the similar query in render_stats().
guests = ( guests = (
UserProfile.objects.filter( UserProfile.objects.filter(
realm=realm, is_active=True, is_bot=False, role=UserProfile.ROLE_GUEST realm=realm, is_active=True, is_bot=False, role=UserProfile.ROLE_GUEST
@@ -248,13 +246,13 @@ class UpgradeWithExistingPlanError(BillingError):
) )
class InvalidBillingScheduleError(Exception): class InvalidBillingSchedule(Exception):
def __init__(self, billing_schedule: int) -> None: def __init__(self, billing_schedule: int) -> None:
self.message = f"Unknown billing_schedule: {billing_schedule}" self.message = f"Unknown billing_schedule: {billing_schedule}"
super().__init__(self.message) super().__init__(self.message)
class InvalidTierError(Exception): class InvalidTier(Exception):
def __init__(self, tier: int) -> None: def __init__(self, tier: int) -> None:
self.message = f"Unknown tier: {tier}" self.message = f"Unknown tier: {tier}"
super().__init__(self.message) super().__init__(self.message)
@@ -570,16 +568,16 @@ def get_price_per_license(
elif billing_schedule == CustomerPlan.MONTHLY: elif billing_schedule == CustomerPlan.MONTHLY:
price_per_license = 800 price_per_license = 800
else: # nocoverage else: # nocoverage
raise InvalidBillingScheduleError(billing_schedule) raise InvalidBillingSchedule(billing_schedule)
elif tier == CustomerPlan.PLUS: elif tier == CustomerPlan.PLUS:
if billing_schedule == CustomerPlan.ANNUAL: if billing_schedule == CustomerPlan.ANNUAL:
price_per_license = 16000 price_per_license = 16000
elif billing_schedule == CustomerPlan.MONTHLY: elif billing_schedule == CustomerPlan.MONTHLY:
price_per_license = 1600 price_per_license = 1600
else: # nocoverage else: # nocoverage
raise InvalidBillingScheduleError(billing_schedule) raise InvalidBillingSchedule(billing_schedule)
else: else:
raise InvalidTierError(tier) raise InvalidTier(tier)
if discount is not None: if discount is not None:
price_per_license = calculate_discounted_price_per_license(price_per_license, discount) price_per_license = calculate_discounted_price_per_license(price_per_license, discount)
@@ -602,7 +600,7 @@ def compute_plan_parameters(
elif billing_schedule == CustomerPlan.MONTHLY: elif billing_schedule == CustomerPlan.MONTHLY:
period_end = add_months(billing_cycle_anchor, 1) period_end = add_months(billing_cycle_anchor, 1)
else: # nocoverage else: # nocoverage
raise InvalidBillingScheduleError(billing_schedule) raise InvalidBillingSchedule(billing_schedule)
price_per_license = get_price_per_license(tier, billing_schedule, discount) price_per_license = get_price_per_license(tier, billing_schedule, discount)

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