mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 16:14:02 +00:00
page_params: Parse page_params and state_data with Zod.
This establishes a runtime check that their types continue to reflect reality going forward. Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
committed by
Tim Abbott
parent
4f1659fe8f
commit
a4938d3760
@@ -80,7 +80,9 @@ def render_stats(
|
|||||||
translation.get_language_from_path(request.path_info),
|
translation.get_language_from_path(request.path_info),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Sync this with stats_params_schema in base_page_params.ts.
|
||||||
page_params = dict(
|
page_params = dict(
|
||||||
|
page_type="stats",
|
||||||
data_url_suffix=data_url_suffix,
|
data_url_suffix=data_url_suffix,
|
||||||
upload_space_used=space_used,
|
upload_space_used=space_used,
|
||||||
guest_users=guest_users,
|
guest_users=guest_users,
|
||||||
|
@@ -8,7 +8,18 @@ from datetime import datetime, timedelta, timezone
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Any, Callable, Dict, Generator, Optional, Tuple, TypedDict, TypeVar, Union
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
Generator,
|
||||||
|
Literal,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
TypedDict,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
from urllib.parse import urlencode, urljoin
|
from urllib.parse import urlencode, urljoin
|
||||||
|
|
||||||
import stripe
|
import stripe
|
||||||
@@ -615,7 +626,9 @@ class BillingSessionAuditLogEventError(Exception):
|
|||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
|
|
||||||
|
# Sync this with upgrade_params_schema in base_page_params.ts.
|
||||||
class UpgradePageParams(TypedDict):
|
class UpgradePageParams(TypedDict):
|
||||||
|
page_type: Literal["upgrade"]
|
||||||
annual_price: int
|
annual_price: int
|
||||||
demo_organization_scheduled_deletion_date: Optional[datetime]
|
demo_organization_scheduled_deletion_date: Optional[datetime]
|
||||||
monthly_price: int
|
monthly_price: int
|
||||||
@@ -2395,6 +2408,7 @@ class BillingSession(ABC):
|
|||||||
"remote_server_legacy_plan_end_date": remote_server_legacy_plan_end_date,
|
"remote_server_legacy_plan_end_date": remote_server_legacy_plan_end_date,
|
||||||
"manual_license_management": initial_upgrade_request.manual_license_management,
|
"manual_license_management": initial_upgrade_request.manual_license_management,
|
||||||
"page_params": {
|
"page_params": {
|
||||||
|
"page_type": "upgrade",
|
||||||
"annual_price": get_price_per_license(
|
"annual_price": get_price_per_license(
|
||||||
tier, CustomerPlan.BILLING_SCHEDULE_ANNUAL, percent_off
|
tier, CustomerPlan.BILLING_SCHEDULE_ANNUAL, percent_off
|
||||||
),
|
),
|
||||||
|
@@ -278,7 +278,9 @@ def team_view(request: HttpRequest) -> HttpResponse:
|
|||||||
request,
|
request,
|
||||||
"corporate/team.html",
|
"corporate/team.html",
|
||||||
context={
|
context={
|
||||||
|
# Sync this with team_params_schema in base_page_params.ts.
|
||||||
"page_params": {
|
"page_params": {
|
||||||
|
"page_type": "team",
|
||||||
"contributors": data["contributors"],
|
"contributors": data["contributors"],
|
||||||
},
|
},
|
||||||
"date": data["date"],
|
"date": data["date"],
|
||||||
|
@@ -56,6 +56,7 @@ EXEMPT_FILES = make_set(
|
|||||||
"web/src/attachments_ui.ts",
|
"web/src/attachments_ui.ts",
|
||||||
"web/src/audible_notifications.ts",
|
"web/src/audible_notifications.ts",
|
||||||
"web/src/avatar.ts",
|
"web/src/avatar.ts",
|
||||||
|
"web/src/base_page_params.ts",
|
||||||
"web/src/billing/event_status.ts",
|
"web/src/billing/event_status.ts",
|
||||||
"web/src/billing/helpers.ts",
|
"web/src/billing/helpers.ts",
|
||||||
"web/src/blueslip.ts",
|
"web/src/blueslip.ts",
|
||||||
|
101
web/src/base_page_params.ts
Normal file
101
web/src/base_page_params.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import $ from "jquery";
|
||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
import {state_data_schema, term_schema} from "./state_data";
|
||||||
|
|
||||||
|
const t1 = performance.now();
|
||||||
|
|
||||||
|
// Sync this with zerver.context_processors.zulip_default_context.
|
||||||
|
const default_params_schema = z.object({
|
||||||
|
page_type: z.literal("default"),
|
||||||
|
development_environment: z.boolean(),
|
||||||
|
realm_sentry_key: z.optional(z.string()),
|
||||||
|
request_language: z.string(),
|
||||||
|
server_sentry_dsn: z.nullable(z.string()),
|
||||||
|
server_sentry_environment: z.optional(z.string()),
|
||||||
|
server_sentry_sample_rate: z.optional(z.number()),
|
||||||
|
server_sentry_trace_rate: z.optional(z.number()),
|
||||||
|
});
|
||||||
|
|
||||||
|
// These parameters are sent in #page-params for both users and spectators.
|
||||||
|
//
|
||||||
|
// Sync this with zerver.lib.home.build_page_params_for_home_page_load.
|
||||||
|
const home_params_schema = default_params_schema
|
||||||
|
.extend({
|
||||||
|
page_type: z.literal("home"),
|
||||||
|
apps_page_url: z.string(),
|
||||||
|
bot_types: z.array(
|
||||||
|
z.object({
|
||||||
|
type_id: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
allowed: z.boolean(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
corporate_enabled: z.boolean(),
|
||||||
|
furthest_read_time: z.nullable(z.number()),
|
||||||
|
is_spectator: z.boolean(),
|
||||||
|
language_list: z.array(
|
||||||
|
z.object({
|
||||||
|
code: z.string(),
|
||||||
|
locale: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
percent_translated: z.optional(z.number()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
login_page: z.string(),
|
||||||
|
narrow: z.optional(z.array(term_schema)),
|
||||||
|
narrow_stream: z.optional(z.string()),
|
||||||
|
needs_tutorial: z.boolean(),
|
||||||
|
promote_sponsoring_zulip: z.boolean(),
|
||||||
|
show_billing: z.boolean(),
|
||||||
|
show_plans: z.boolean(),
|
||||||
|
show_webathena: z.boolean(),
|
||||||
|
sponsorship_pending: z.boolean(),
|
||||||
|
state_data: state_data_schema.optional(),
|
||||||
|
translation_data: z.record(z.string()),
|
||||||
|
})
|
||||||
|
// TODO/typescript: Remove .passthrough() when all consumers have been
|
||||||
|
// converted to TypeScript and the schema is complete.
|
||||||
|
.passthrough();
|
||||||
|
|
||||||
|
// Sync this with analytics.views.stats.render_stats.
|
||||||
|
const stats_params_schema = default_params_schema.extend({
|
||||||
|
page_type: z.literal("stats"),
|
||||||
|
data_url_suffix: z.string(),
|
||||||
|
upload_space_used: z.nullable(z.number()),
|
||||||
|
guest_users: z.nullable(z.number()),
|
||||||
|
translation_data: z.record(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sync this with corporate.views.portico.team_view.
|
||||||
|
const team_params_schema = default_params_schema.extend({
|
||||||
|
page_type: z.literal("team"),
|
||||||
|
contributors: z.unknown(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sync this with corporate.lib.stripe.UpgradePageParams.
|
||||||
|
const upgrade_params_schema = default_params_schema.extend({
|
||||||
|
page_type: z.literal("upgrade"),
|
||||||
|
annual_price: z.number(),
|
||||||
|
demo_organization_scheduled_deletion_date: z.nullable(z.number()),
|
||||||
|
monthly_price: z.number(),
|
||||||
|
seat_count: z.number(),
|
||||||
|
billing_base_url: z.string(),
|
||||||
|
tier: z.number(),
|
||||||
|
flat_discount: z.number(),
|
||||||
|
flat_discounted_months: z.number(),
|
||||||
|
fixed_price: z.number().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const page_params_schema = z.discriminatedUnion("page_type", [
|
||||||
|
default_params_schema,
|
||||||
|
home_params_schema,
|
||||||
|
stats_params_schema,
|
||||||
|
team_params_schema,
|
||||||
|
upgrade_params_schema,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const page_params = page_params_schema.parse($("#page-params").remove().data("params"));
|
||||||
|
|
||||||
|
const t2 = performance.now();
|
||||||
|
export const page_params_parse_time = t2 - t1;
|
@@ -1,19 +1,9 @@
|
|||||||
import $ from "jquery";
|
import assert from "minimalistic-assert";
|
||||||
|
|
||||||
// Don't remove page_params here yet, since we still use them later.
|
import {page_params as base_page_params} from "../base_page_params";
|
||||||
// For example, "#page_params" is used again through `sentry.ts`, which
|
|
||||||
// imports the main `src/page_params` module.
|
|
||||||
export const page_params: {
|
|
||||||
annual_price: number;
|
|
||||||
monthly_price: number;
|
|
||||||
seat_count: number;
|
|
||||||
billing_base_url: string;
|
|
||||||
tier: number;
|
|
||||||
flat_discount: number;
|
|
||||||
flat_discounted_months: number;
|
|
||||||
fixed_price: number | null;
|
|
||||||
} = $("#page-params").data("params");
|
|
||||||
|
|
||||||
if (!page_params) {
|
assert(base_page_params.page_type === "upgrade");
|
||||||
throw new Error("Missing page-params");
|
|
||||||
}
|
// We need to export with a narrowed TypeScript type
|
||||||
|
// eslint-disable-next-line unicorn/prefer-export-from
|
||||||
|
export const page_params = base_page_params;
|
||||||
|
@@ -9,8 +9,8 @@
|
|||||||
import * as Sentry from "@sentry/browser";
|
import * as Sentry from "@sentry/browser";
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
|
||||||
|
import {page_params} from "./base_page_params";
|
||||||
import {BlueslipError, display_stacktrace} from "./blueslip_stacktrace";
|
import {BlueslipError, display_stacktrace} from "./blueslip_stacktrace";
|
||||||
import {page_params} from "./page_params";
|
|
||||||
|
|
||||||
if (Error.stackTraceLimit !== undefined) {
|
if (Error.stackTraceLimit !== undefined) {
|
||||||
Error.stackTraceLimit = 100000;
|
Error.stackTraceLimit = 100000;
|
||||||
|
@@ -12,6 +12,7 @@ import type {Message} from "./message_store";
|
|||||||
import {page_params} from "./page_params";
|
import {page_params} from "./page_params";
|
||||||
import * as people from "./people";
|
import * as people from "./people";
|
||||||
import {realm} from "./state_data";
|
import {realm} from "./state_data";
|
||||||
|
import type {Term} from "./state_data";
|
||||||
import * as stream_data from "./stream_data";
|
import * as stream_data from "./stream_data";
|
||||||
import type {StreamSubscription} from "./sub_store";
|
import type {StreamSubscription} from "./sub_store";
|
||||||
import * as unread from "./unread";
|
import * as unread from "./unread";
|
||||||
@@ -234,12 +235,6 @@ function message_matches_search_term(message: Message, operator: string, operand
|
|||||||
return true; // unknown operators return true (effectively ignored)
|
return true; // unknown operators return true (effectively ignored)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Term = {
|
|
||||||
negated?: boolean;
|
|
||||||
operator: string;
|
|
||||||
operand: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Filter {
|
export class Filter {
|
||||||
_terms: Term[];
|
_terms: Term[];
|
||||||
_sub?: StreamSubscription;
|
_sub?: StreamSubscription;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import {realm} from "./state_data";
|
import {realm} from "./state_data";
|
||||||
import type {GroupPermissionSetting} from "./types";
|
import type {GroupPermissionSetting} from "./state_data";
|
||||||
|
|
||||||
export function get_group_permission_setting_config(
|
export function get_group_permission_setting_config(
|
||||||
setting_name: string,
|
setting_name: string,
|
||||||
|
@@ -6,14 +6,14 @@ import {DEFAULT_INTL_CONFIG, IntlErrorCode, createIntl, createIntlCache} from "@
|
|||||||
import type {FormatXMLElementFn, PrimitiveType} from "intl-messageformat";
|
import type {FormatXMLElementFn, PrimitiveType} from "intl-messageformat";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
import {page_params} from "./page_params";
|
import {page_params} from "./base_page_params";
|
||||||
|
|
||||||
const cache = createIntlCache();
|
const cache = createIntlCache();
|
||||||
export const intl = createIntl(
|
export const intl = createIntl(
|
||||||
{
|
{
|
||||||
locale: page_params.request_language,
|
locale: page_params.request_language,
|
||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
messages: page_params.translation_data,
|
messages: "translation_data" in page_params ? page_params.translation_data : {},
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
onError(error) {
|
onError(error) {
|
||||||
// Ignore complaints about untranslated strings that were
|
// Ignore complaints about untranslated strings that were
|
||||||
@@ -50,7 +50,7 @@ export function $t_html(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export let language_list: (typeof page_params)["language_list"];
|
export let language_list: (typeof page_params & {page_type: "home"})["language_list"];
|
||||||
|
|
||||||
export function get_language_name(language_code: string): string {
|
export function get_language_name(language_code: string): string {
|
||||||
const language_list_map: Record<string, string> = {};
|
const language_list_map: Record<string, string> = {};
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import * as blueslip from "./blueslip";
|
import * as blueslip from "./blueslip";
|
||||||
import {Filter} from "./filter";
|
import {Filter} from "./filter";
|
||||||
import type {Term} from "./filter";
|
|
||||||
import * as inbox_util from "./inbox_util";
|
import * as inbox_util from "./inbox_util";
|
||||||
import {page_params} from "./page_params";
|
import {page_params} from "./page_params";
|
||||||
import * as people from "./people";
|
import * as people from "./people";
|
||||||
import * as recent_view_util from "./recent_view_util";
|
import * as recent_view_util from "./recent_view_util";
|
||||||
|
import type {Term} from "./state_data";
|
||||||
import * as stream_data from "./stream_data";
|
import * as stream_data from "./stream_data";
|
||||||
import type {StreamSubscription} from "./sub_store";
|
import type {StreamSubscription} from "./sub_store";
|
||||||
import * as unread from "./unread";
|
import * as unread from "./unread";
|
||||||
|
@@ -1,44 +1,9 @@
|
|||||||
import $ from "jquery";
|
import assert from "minimalistic-assert";
|
||||||
|
|
||||||
import type {Term} from "./filter";
|
import {page_params as base_page_params} from "./base_page_params";
|
||||||
|
|
||||||
const t1 = performance.now();
|
assert(base_page_params.page_type === "home");
|
||||||
export const page_params: {
|
|
||||||
apps_page_url: string;
|
// We need to export with a narrowed TypeScript type.
|
||||||
bot_types: {
|
// eslint-disable-next-line unicorn/prefer-export-from
|
||||||
type_id: number;
|
export const page_params = base_page_params;
|
||||||
name: string;
|
|
||||||
allowed: boolean;
|
|
||||||
}[];
|
|
||||||
corporate_enabled: boolean;
|
|
||||||
development_environment: boolean;
|
|
||||||
furthest_read_time: number | null;
|
|
||||||
is_spectator: boolean;
|
|
||||||
language_list: {
|
|
||||||
code: string;
|
|
||||||
locale: string;
|
|
||||||
name: string;
|
|
||||||
percent_translated?: number;
|
|
||||||
}[];
|
|
||||||
login_page: string;
|
|
||||||
narrow?: Term[];
|
|
||||||
narrow_stream?: string;
|
|
||||||
needs_tutorial: boolean;
|
|
||||||
promote_sponsoring_zulip: boolean;
|
|
||||||
realm_sentry_key?: string;
|
|
||||||
request_language: string;
|
|
||||||
server_sentry_dsn: string | null;
|
|
||||||
server_sentry_environment?: string;
|
|
||||||
server_sentry_sample_rate?: number;
|
|
||||||
server_sentry_trace_rate?: number;
|
|
||||||
show_billing: boolean;
|
|
||||||
show_plans: boolean;
|
|
||||||
show_webathena: boolean;
|
|
||||||
sponsorship_pending: boolean;
|
|
||||||
translation_data: Record<string, string>;
|
|
||||||
} = $("#page-params").remove().data("params");
|
|
||||||
const t2 = performance.now();
|
|
||||||
export const page_params_parse_time = t2 - t1;
|
|
||||||
if (!page_params) {
|
|
||||||
throw new Error("Missing page-params");
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import {gtag, install} from "ga-gtag";
|
import {gtag, install} from "ga-gtag";
|
||||||
|
|
||||||
import {page_params} from "../page_params";
|
import {page_params} from "../base_page_params";
|
||||||
|
|
||||||
export let config;
|
export let config;
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
import assert from "minimalistic-assert";
|
||||||
|
|
||||||
import {page_params} from "../page_params";
|
import {page_params} from "../base_page_params";
|
||||||
|
|
||||||
import {detect_user_os} from "./tabbed-instructions";
|
import {detect_user_os} from "./tabbed-instructions";
|
||||||
import render_tabs from "./team";
|
import render_tabs from "./team";
|
||||||
@@ -119,6 +120,7 @@ $(() => {
|
|||||||
events();
|
events();
|
||||||
|
|
||||||
if (window.location.pathname === "/team/") {
|
if (window.location.pathname === "/team/") {
|
||||||
|
assert(page_params.page_type === "team");
|
||||||
const contributors = page_params.contributors;
|
const contributors = page_params.contributors;
|
||||||
delete page_params.contributors;
|
delete page_params.contributors;
|
||||||
render_tabs(contributors);
|
render_tabs(contributors);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import * as Sentry from "@sentry/browser";
|
import * as Sentry from "@sentry/browser";
|
||||||
|
|
||||||
import {page_params} from "./page_params";
|
import {page_params} from "./base_page_params";
|
||||||
import {current_user, realm} from "./state_data";
|
import {current_user, realm} from "./state_data";
|
||||||
|
|
||||||
type UserInfo = {
|
type UserInfo = {
|
||||||
@@ -76,7 +76,11 @@ if (page_params.server_sentry_dsn) {
|
|||||||
const user_info: UserInfo = {
|
const user_info: UserInfo = {
|
||||||
realm: sentry_key,
|
realm: sentry_key,
|
||||||
};
|
};
|
||||||
if (sentry_key !== "www" && current_user !== undefined) {
|
if (
|
||||||
|
sentry_key !== "www" &&
|
||||||
|
page_params.page_type === "home" &&
|
||||||
|
current_user !== undefined
|
||||||
|
) {
|
||||||
user_info.role = current_user.is_owner
|
user_info.role = current_user.is_owner
|
||||||
? "Organization owner"
|
? "Organization owner"
|
||||||
: current_user.is_admin
|
: current_user.is_admin
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import Handlebars from "handlebars/runtime";
|
import Handlebars from "handlebars/runtime";
|
||||||
|
|
||||||
|
import {page_params} from "./base_page_params";
|
||||||
import {$t, $t_html} from "./i18n";
|
import {$t, $t_html} from "./i18n";
|
||||||
import {page_params} from "./page_params";
|
|
||||||
import type {RealmDefaultSettings} from "./realm_user_settings_defaults";
|
import type {RealmDefaultSettings} from "./realm_user_settings_defaults";
|
||||||
import {realm} from "./state_data";
|
import {realm} from "./state_data";
|
||||||
import type {StreamSpecificNotificationSettings} from "./sub_store";
|
import type {StreamSpecificNotificationSettings} from "./sub_store";
|
||||||
|
@@ -1,131 +1,145 @@
|
|||||||
import type {GroupPermissionSetting} from "./types";
|
import {z} from "zod";
|
||||||
|
|
||||||
export let current_user: {
|
const group_permission_setting_schema = z.object({
|
||||||
avatar_source: string;
|
require_system_group: z.boolean(),
|
||||||
delivery_email: string;
|
allow_internet_group: z.boolean(),
|
||||||
is_admin: boolean;
|
allow_owners_group: z.boolean(),
|
||||||
is_billing_admin: boolean;
|
allow_nobody_group: z.boolean(),
|
||||||
is_guest: boolean;
|
allow_everyone_group: z.boolean(),
|
||||||
is_moderator: boolean;
|
default_group_name: z.string(),
|
||||||
is_owner: boolean;
|
id_field_name: z.string(),
|
||||||
user_id: number;
|
default_for_system_groups: z.nullable(z.string()),
|
||||||
};
|
allowed_system_groups: z.array(z.string()),
|
||||||
|
});
|
||||||
|
export type GroupPermissionSetting = z.output<typeof group_permission_setting_schema>;
|
||||||
|
|
||||||
export let realm: {
|
export const term_schema = z.object({
|
||||||
custom_profile_fields: {
|
negated: z.optional(z.boolean()),
|
||||||
display_in_profile_summary?: boolean;
|
operator: z.string(),
|
||||||
field_data: string;
|
operand: z.string(),
|
||||||
hint: string;
|
});
|
||||||
id: number;
|
export type Term = z.output<typeof term_schema>;
|
||||||
name: string;
|
// Sync this with zerver.lib.events.do_events_register.
|
||||||
order: number;
|
|
||||||
type: number;
|
|
||||||
}[];
|
|
||||||
custom_profile_field_types: {
|
|
||||||
SHORT_TEXT: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
LONG_TEXT: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
DATE: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
SELECT: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
URL: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
EXTERNAL_ACCOUNT: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
USER: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
PRONOUNS: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
max_avatar_file_size_mib: number;
|
|
||||||
max_icon_file_size_mib: number;
|
|
||||||
max_logo_file_size_mib: number;
|
|
||||||
realm_add_custom_emoji_policy: number;
|
|
||||||
realm_available_video_chat_providers: {
|
|
||||||
disabled: {name: string; id: number};
|
|
||||||
jitsi_meet: {name: string; id: number};
|
|
||||||
zoom?: {name: string; id: number};
|
|
||||||
big_blue_button?: {name: string; id: number};
|
|
||||||
};
|
|
||||||
realm_avatar_changes_disabled: boolean;
|
|
||||||
realm_bot_domain: string;
|
|
||||||
realm_can_access_all_users_group: number;
|
|
||||||
realm_create_multiuse_invite_group: number;
|
|
||||||
realm_create_private_stream_policy: number;
|
|
||||||
realm_create_public_stream_policy: number;
|
|
||||||
realm_create_web_public_stream_policy: number;
|
|
||||||
realm_delete_own_message_policy: number;
|
|
||||||
realm_description: string;
|
|
||||||
realm_domains: {domain: string; allow_subdomains: boolean}[];
|
|
||||||
realm_edit_topic_policy: number;
|
|
||||||
realm_email_changes_disabled: boolean;
|
|
||||||
realm_enable_guest_user_indicator: boolean;
|
|
||||||
realm_enable_spectator_access: boolean;
|
|
||||||
realm_icon_source: string;
|
|
||||||
realm_icon_url: string;
|
|
||||||
realm_invite_to_realm_policy: number;
|
|
||||||
realm_invite_to_stream_policy: number;
|
|
||||||
realm_is_zephyr_mirror_realm: boolean;
|
|
||||||
realm_jitsi_server_url: string | null;
|
|
||||||
realm_logo_source: string;
|
|
||||||
realm_logo_url: string;
|
|
||||||
realm_move_messages_between_streams_policy: number;
|
|
||||||
realm_name_changes_disabled: boolean;
|
|
||||||
realm_name: string;
|
|
||||||
realm_night_logo_source: string;
|
|
||||||
realm_night_logo_url: string;
|
|
||||||
realm_notifications_stream_id: number;
|
|
||||||
realm_org_type: number;
|
|
||||||
realm_plan_type: number;
|
|
||||||
realm_private_message_policy: number;
|
|
||||||
realm_push_notifications_enabled: boolean;
|
|
||||||
realm_upload_quota_mib: number | null;
|
|
||||||
realm_uri: string;
|
|
||||||
realm_user_group_edit_policy: number;
|
|
||||||
realm_video_chat_provider: number;
|
|
||||||
realm_waiting_period_threshold: number;
|
|
||||||
server_avatar_changes_disabled: boolean;
|
|
||||||
server_jitsi_server_url: string | null;
|
|
||||||
server_name_changes_disabled: boolean;
|
|
||||||
server_needs_upgrade: boolean;
|
|
||||||
server_presence_offline_threshold_seconds: number;
|
|
||||||
server_supported_permission_settings: {
|
|
||||||
realm: Record<string, GroupPermissionSetting>;
|
|
||||||
stream: Record<string, GroupPermissionSetting>;
|
|
||||||
group: Record<string, GroupPermissionSetting>;
|
|
||||||
};
|
|
||||||
server_typing_started_expiry_period_milliseconds: number;
|
|
||||||
server_typing_started_wait_period_milliseconds: number;
|
|
||||||
server_typing_stopped_wait_period_milliseconds: number;
|
|
||||||
server_web_public_streams_enabled: boolean;
|
|
||||||
stop_words: string[];
|
|
||||||
zulip_merge_base: string;
|
|
||||||
zulip_plan_is_not_limited: boolean;
|
|
||||||
zulip_version: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function set_current_user(initial_current_user: typeof current_user): void {
|
export const current_user_schema = z.object({
|
||||||
|
avatar_source: z.string(),
|
||||||
|
delivery_email: z.string(),
|
||||||
|
is_admin: z.boolean(),
|
||||||
|
is_billing_admin: z.boolean(),
|
||||||
|
is_guest: z.boolean(),
|
||||||
|
is_moderator: z.boolean(),
|
||||||
|
is_owner: z.boolean(),
|
||||||
|
user_id: z.number(),
|
||||||
|
});
|
||||||
|
// Sync this with zerver.lib.events.do_events_register.
|
||||||
|
|
||||||
|
export const realm_schema = z.object({
|
||||||
|
custom_profile_fields: z.array(
|
||||||
|
z.object({
|
||||||
|
display_in_profile_summary: z.optional(z.boolean()),
|
||||||
|
field_data: z.string(),
|
||||||
|
hint: z.string(),
|
||||||
|
id: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
order: z.number(),
|
||||||
|
type: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
custom_profile_field_types: z.object({
|
||||||
|
SHORT_TEXT: z.object({id: z.number(), name: z.string()}),
|
||||||
|
LONG_TEXT: z.object({id: z.number(), name: z.string()}),
|
||||||
|
DATE: z.object({id: z.number(), name: z.string()}),
|
||||||
|
SELECT: z.object({id: z.number(), name: z.string()}),
|
||||||
|
URL: z.object({id: z.number(), name: z.string()}),
|
||||||
|
EXTERNAL_ACCOUNT: z.object({id: z.number(), name: z.string()}),
|
||||||
|
USER: z.object({id: z.number(), name: z.string()}),
|
||||||
|
PRONOUNS: z.object({id: z.number(), name: z.string()}),
|
||||||
|
}),
|
||||||
|
max_avatar_file_size_mib: z.number(),
|
||||||
|
max_icon_file_size_mib: z.number(),
|
||||||
|
max_logo_file_size_mib: z.number(),
|
||||||
|
realm_add_custom_emoji_policy: z.number(),
|
||||||
|
realm_available_video_chat_providers: z.object({
|
||||||
|
disabled: z.object({name: z.string(), id: z.number()}),
|
||||||
|
jitsi_meet: z.object({name: z.string(), id: z.number()}),
|
||||||
|
zoom: z.optional(z.object({name: z.string(), id: z.number()})),
|
||||||
|
big_blue_button: z.optional(z.object({name: z.string(), id: z.number()})),
|
||||||
|
}),
|
||||||
|
realm_avatar_changes_disabled: z.boolean(),
|
||||||
|
realm_bot_domain: z.string(),
|
||||||
|
realm_can_access_all_users_group: z.number(),
|
||||||
|
realm_create_multiuse_invite_group: z.number(),
|
||||||
|
realm_create_private_stream_policy: z.number(),
|
||||||
|
realm_create_public_stream_policy: z.number(),
|
||||||
|
realm_create_web_public_stream_policy: z.number(),
|
||||||
|
realm_delete_own_message_policy: z.number(),
|
||||||
|
realm_description: z.string(),
|
||||||
|
realm_domains: z.array(
|
||||||
|
z.object({
|
||||||
|
domain: z.string(),
|
||||||
|
allow_subdomains: z.boolean(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
realm_edit_topic_policy: z.number(),
|
||||||
|
realm_email_changes_disabled: z.boolean(),
|
||||||
|
realm_enable_guest_user_indicator: z.boolean(),
|
||||||
|
realm_enable_spectator_access: z.boolean(),
|
||||||
|
realm_icon_source: z.string(),
|
||||||
|
realm_icon_url: z.string(),
|
||||||
|
realm_invite_to_realm_policy: z.number(),
|
||||||
|
realm_invite_to_stream_policy: z.number(),
|
||||||
|
realm_is_zephyr_mirror_realm: z.boolean(),
|
||||||
|
realm_jitsi_server_url: z.nullable(z.string()),
|
||||||
|
realm_logo_source: z.string(),
|
||||||
|
realm_logo_url: z.string(),
|
||||||
|
realm_move_messages_between_streams_policy: z.number(),
|
||||||
|
realm_name_changes_disabled: z.boolean(),
|
||||||
|
realm_name: z.string(),
|
||||||
|
realm_night_logo_source: z.string(),
|
||||||
|
realm_night_logo_url: z.string(),
|
||||||
|
realm_notifications_stream_id: z.number(),
|
||||||
|
realm_org_type: z.number(),
|
||||||
|
realm_plan_type: z.number(),
|
||||||
|
realm_private_message_policy: z.number(),
|
||||||
|
realm_push_notifications_enabled: z.boolean(),
|
||||||
|
realm_upload_quota_mib: z.nullable(z.number()),
|
||||||
|
realm_uri: z.string(),
|
||||||
|
realm_user_group_edit_policy: z.number(),
|
||||||
|
realm_video_chat_provider: z.number(),
|
||||||
|
realm_waiting_period_threshold: z.number(),
|
||||||
|
server_avatar_changes_disabled: z.boolean(),
|
||||||
|
server_jitsi_server_url: z.nullable(z.string()),
|
||||||
|
server_name_changes_disabled: z.boolean(),
|
||||||
|
server_needs_upgrade: z.boolean(),
|
||||||
|
server_presence_offline_threshold_seconds: z.number(),
|
||||||
|
server_supported_permission_settings: z.object({
|
||||||
|
realm: z.record(group_permission_setting_schema),
|
||||||
|
stream: z.record(group_permission_setting_schema),
|
||||||
|
group: z.record(group_permission_setting_schema),
|
||||||
|
}),
|
||||||
|
server_typing_started_expiry_period_milliseconds: z.number(),
|
||||||
|
server_typing_started_wait_period_milliseconds: z.number(),
|
||||||
|
server_typing_stopped_wait_period_milliseconds: z.number(),
|
||||||
|
server_web_public_streams_enabled: z.boolean(),
|
||||||
|
stop_words: z.array(z.string()),
|
||||||
|
zulip_merge_base: z.string(),
|
||||||
|
zulip_plan_is_not_limited: z.boolean(),
|
||||||
|
zulip_version: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const state_data_schema = current_user_schema
|
||||||
|
.merge(realm_schema)
|
||||||
|
// TODO/typescript: Remove .passthrough() when all consumers have been
|
||||||
|
// converted to TypeScript and the schema is complete.
|
||||||
|
.passthrough();
|
||||||
|
|
||||||
|
export let current_user: z.infer<typeof current_user_schema>;
|
||||||
|
export let realm: z.infer<typeof realm_schema>;
|
||||||
|
|
||||||
|
export function set_current_user(initial_current_user: z.infer<typeof current_user_schema>): void {
|
||||||
current_user = initial_current_user;
|
current_user = initial_current_user;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function set_realm(initial_realm: typeof realm): void {
|
export function set_realm(initial_realm: z.infer<typeof realm_schema>): void {
|
||||||
realm = initial_realm;
|
realm = initial_realm;
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
import $ from "jquery";
|
import assert from "minimalistic-assert";
|
||||||
|
|
||||||
export const page_params: {
|
import {page_params as base_page_params} from "../base_page_params";
|
||||||
data_url_suffix: string;
|
|
||||||
guest_users: number | null;
|
|
||||||
upload_space_used: number | null;
|
|
||||||
} = $("#page-params").data("params");
|
|
||||||
|
|
||||||
if (!page_params) {
|
assert(base_page_params.page_type === "stats");
|
||||||
throw new Error("Missing page-params");
|
|
||||||
}
|
// We need to export with a narrowed TypeScript type
|
||||||
|
// eslint-disable-next-line unicorn/prefer-export-from
|
||||||
|
export const page_params = base_page_params;
|
||||||
|
@@ -52,15 +52,3 @@ export type UpdateMessageEvent = {
|
|||||||
// This will not be set until it gets fixed.
|
// This will not be set until it gets fixed.
|
||||||
topic?: string;
|
topic?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GroupPermissionSetting = {
|
|
||||||
require_system_group: boolean;
|
|
||||||
allow_internet_group: boolean;
|
|
||||||
allow_owners_group: boolean;
|
|
||||||
allow_nobody_group: boolean;
|
|
||||||
allow_everyone_group: boolean;
|
|
||||||
default_group_name: string;
|
|
||||||
id_field_name: string;
|
|
||||||
default_for_system_groups: string | null;
|
|
||||||
allowed_system_groups: string[];
|
|
||||||
};
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import assert from "minimalistic-assert";
|
||||||
|
|
||||||
import generated_emoji_codes from "../../static/generated/emoji/emoji_codes.json";
|
import generated_emoji_codes from "../../static/generated/emoji/emoji_codes.json";
|
||||||
import * as fenced_code from "../shared/src/fenced_code";
|
import * as fenced_code from "../shared/src/fenced_code";
|
||||||
@@ -114,7 +115,7 @@ import * as sidebar_ui from "./sidebar_ui";
|
|||||||
import * as spoilers from "./spoilers";
|
import * as spoilers from "./spoilers";
|
||||||
import * as starred_messages from "./starred_messages";
|
import * as starred_messages from "./starred_messages";
|
||||||
import * as starred_messages_ui from "./starred_messages_ui";
|
import * as starred_messages_ui from "./starred_messages_ui";
|
||||||
import {current_user, realm, set_current_user, set_realm} from "./state_data";
|
import {current_user, realm, set_current_user, set_realm, state_data_schema} from "./state_data";
|
||||||
import * as stream_data from "./stream_data";
|
import * as stream_data from "./stream_data";
|
||||||
import * as stream_edit from "./stream_edit";
|
import * as stream_edit from "./stream_edit";
|
||||||
import * as stream_edit_subscribers from "./stream_edit_subscribers";
|
import * as stream_edit_subscribers from "./stream_edit_subscribers";
|
||||||
@@ -874,7 +875,8 @@ $(async () => {
|
|||||||
url: "/json/register",
|
url: "/json/register",
|
||||||
data,
|
data,
|
||||||
success(response_data) {
|
success(response_data) {
|
||||||
initialize_everything(response_data);
|
const state_data = state_data_schema.parse(response_data);
|
||||||
|
initialize_everything(state_data);
|
||||||
},
|
},
|
||||||
error() {
|
error() {
|
||||||
$("#app-loading-middle-content").hide();
|
$("#app-loading-middle-content").hide();
|
||||||
@@ -884,6 +886,7 @@ $(async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
assert(page_params.state_data !== undefined);
|
||||||
initialize_everything(page_params.state_data);
|
initialize_everything(page_params.state_data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -9,7 +9,7 @@ export {get_stream_id, get_sub, get_subscriber_count} from "./stream_data";
|
|||||||
export {get_by_user_id as get_person_by_user_id, get_user_id_from_name} from "./people";
|
export {get_by_user_id as get_person_by_user_id, get_user_id_from_name} from "./people";
|
||||||
export {last_visible as last_visible_row, id as row_id} from "./rows";
|
export {last_visible as last_visible_row, id as row_id} from "./rows";
|
||||||
export {cancel as cancel_compose} from "./compose_actions";
|
export {cancel as cancel_compose} from "./compose_actions";
|
||||||
export {page_params, page_params_parse_time} from "./page_params";
|
export {page_params, page_params_parse_time} from "./base_page_params";
|
||||||
export {initiate as initiate_reload} from "./reload";
|
export {initiate as initiate_reload} from "./reload";
|
||||||
export {page_load_time} from "./setup";
|
export {page_load_time} from "./setup";
|
||||||
export {current_user, realm} from "./state_data";
|
export {current_user, realm} from "./state_data";
|
||||||
|
@@ -118,6 +118,8 @@ test.set_verbose(files.length === 1);
|
|||||||
require("../../src/blueslip");
|
require("../../src/blueslip");
|
||||||
namespace.mock_esm("../../src/i18n", stub_i18n);
|
namespace.mock_esm("../../src/i18n", stub_i18n);
|
||||||
require("../../src/i18n");
|
require("../../src/i18n");
|
||||||
|
namespace.mock_esm("../../src/base_page_params", zpage_params);
|
||||||
|
require("../../src/base_page_params");
|
||||||
namespace.mock_esm("../../src/billing/page_params", zpage_billing_params);
|
namespace.mock_esm("../../src/billing/page_params", zpage_billing_params);
|
||||||
require("../../src/billing/page_params");
|
require("../../src/billing/page_params");
|
||||||
namespace.mock_esm("../../src/page_params", zpage_params);
|
namespace.mock_esm("../../src/page_params", zpage_params);
|
||||||
|
@@ -32,6 +32,7 @@ from zproject.backends import (
|
|||||||
from zproject.config import get_config
|
from zproject.config import get_config
|
||||||
|
|
||||||
DEFAULT_PAGE_PARAMS: Mapping[str, Any] = {
|
DEFAULT_PAGE_PARAMS: Mapping[str, Any] = {
|
||||||
|
"page_type": "default",
|
||||||
"development_environment": settings.DEVELOPMENT,
|
"development_environment": settings.DEVELOPMENT,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +166,7 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
|
|||||||
f'<a href="mailto:{escape(support_email)}">{escape(support_email)}</a>'
|
f'<a href="mailto:{escape(support_email)}">{escape(support_email)}</a>'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Sync this with default_params_schema in base_page_params.ts.
|
||||||
default_page_params: Dict[str, Any] = {
|
default_page_params: Dict[str, Any] = {
|
||||||
**DEFAULT_PAGE_PARAMS,
|
**DEFAULT_PAGE_PARAMS,
|
||||||
"server_sentry_dsn": settings.SENTRY_FRONTEND_DSN,
|
"server_sentry_dsn": settings.SENTRY_FRONTEND_DSN,
|
||||||
|
@@ -195,7 +195,10 @@ def build_page_params_for_home_page_load(
|
|||||||
|
|
||||||
# Pass parameters to the client-side JavaScript code.
|
# Pass parameters to the client-side JavaScript code.
|
||||||
# These end up in a JavaScript Object named 'page_params'.
|
# These end up in a JavaScript Object named 'page_params'.
|
||||||
|
#
|
||||||
|
# Sync this with home_params_schema in base_page_params.ts.
|
||||||
page_params: Dict[str, object] = dict(
|
page_params: Dict[str, object] = dict(
|
||||||
|
page_type="home",
|
||||||
## Server settings.
|
## Server settings.
|
||||||
test_suite=settings.TEST_SUITE,
|
test_suite=settings.TEST_SUITE,
|
||||||
insecure_desktop_app=insecure_desktop_app,
|
insecure_desktop_app=insecure_desktop_app,
|
||||||
|
@@ -52,6 +52,7 @@ class HomeTest(ZulipTestCase):
|
|||||||
"narrow_stream",
|
"narrow_stream",
|
||||||
"needs_tutorial",
|
"needs_tutorial",
|
||||||
"no_event_queue",
|
"no_event_queue",
|
||||||
|
"page_type",
|
||||||
"promote_sponsoring_zulip",
|
"promote_sponsoring_zulip",
|
||||||
"request_language",
|
"request_language",
|
||||||
"server_sentry_dsn",
|
"server_sentry_dsn",
|
||||||
@@ -347,6 +348,7 @@ class HomeTest(ZulipTestCase):
|
|||||||
"login_page",
|
"login_page",
|
||||||
"needs_tutorial",
|
"needs_tutorial",
|
||||||
"no_event_queue",
|
"no_event_queue",
|
||||||
|
"page_type",
|
||||||
"promote_sponsoring_zulip",
|
"promote_sponsoring_zulip",
|
||||||
"realm_rendered_description",
|
"realm_rendered_description",
|
||||||
"request_language",
|
"request_language",
|
||||||
|
Reference in New Issue
Block a user