mirror of https://github.com/zulip/zulip.git
linkifiers: Update API to send data using dictionaries.
* This introduces a new event type `realm_linkifiers` and a new key for the initial data fetch of the same name. Newer clients will be expected to use these. * Backwards compatibility is ensured by changing neither the current event nor the /register key. The data which these hold is the same as before, but internally, it is generated by processing the `realm_linkifiers` data. We send both the old and the new event types to clients whenever the linkifiers are changed. Older clients will simply ignore the new event type, and vice versa. * The `realm/filters:GET` endpoint (which returns tuples) is currently used by none of the official Zulip clients. This commit replaces it with `realm/linkifiers:GET` which returns data in the new dictionary format. TODO: Update the `get_realm_filters` method in the API bindings, to hit this new URL instead of the old one. * This also updates the webapp frontend to use the newer events and keys.
This commit is contained in:
parent
5eff43f5d9
commit
3947b0c80a
|
@ -516,13 +516,13 @@ run_test("realm_emoji", (override) => {
|
|||
}
|
||||
});
|
||||
|
||||
run_test("linkifier", (override) => {
|
||||
const event = event_fixtures.realm_filters;
|
||||
page_params.realm_filters = [];
|
||||
run_test("realm_linkifiers", (override) => {
|
||||
const event = event_fixtures.realm_linkifiers;
|
||||
page_params.realm_linkifiers = [];
|
||||
override(settings_linkifiers, "populate_linkifiers", noop);
|
||||
override(markdown, "update_linkifier_rules", noop);
|
||||
dispatch(event);
|
||||
assert_same(page_params.realm_filters, event.realm_filters);
|
||||
assert_same(page_params.realm_linkifiers, event.realm_linkifiers);
|
||||
});
|
||||
|
||||
run_test("realm_domains", (override) => {
|
||||
|
|
|
@ -456,9 +456,15 @@ exports.fixtures = {
|
|||
],
|
||||
},
|
||||
|
||||
realm_filters: {
|
||||
type: "realm_filters",
|
||||
realm_filters: [["#[123]", "ticket %(id)s", 55]],
|
||||
realm_linkifiers: {
|
||||
type: "realm_linkifiers",
|
||||
realm_linkifiers: [
|
||||
{
|
||||
pattern: "#[123]",
|
||||
url_format: "ticket %(id)s",
|
||||
id: 55,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
realm_user__add: {
|
||||
|
|
|
@ -13,13 +13,22 @@ set_global("location", {
|
|||
origin: "http://zulip.zulipdev.com",
|
||||
});
|
||||
|
||||
const example_realm_filters = [
|
||||
["#(?P<id>[0-9]{2,8})", "https://trac.example.com/ticket/%(id)s"],
|
||||
["ZBUG_(?P<id>[0-9]{2,8})", "https://trac2.zulip.net/ticket/%(id)s"],
|
||||
[
|
||||
"ZGROUP_(?P<id>[0-9]{2,8}):(?P<zone>[0-9]{1,8})",
|
||||
"https://zone_%(zone)s.zulip.net/ticket/%(id)s",
|
||||
],
|
||||
const example_realm_linkifiers = [
|
||||
{
|
||||
pattern: "#(?P<id>[0-9]{2,8})",
|
||||
url_format: "https://trac.example.com/ticket/%(id)s",
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
pattern: "ZBUG_(?P<id>[0-9]{2,8})",
|
||||
url_format: "https://trac2.zulip.net/ticket/%(id)s",
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
pattern: "ZGROUP_(?P<id>[0-9]{2,8}):(?P<zone>[0-9]{1,8})",
|
||||
url_format: "https://zone_%(zone)s.zulip.net/ticket/%(id)s",
|
||||
id: 3,
|
||||
},
|
||||
];
|
||||
page_params.translate_emoticons = false;
|
||||
|
||||
|
@ -179,12 +188,12 @@ stream_data.add_sub(edgecase_stream_2);
|
|||
// streamTopicHandler and it would be parsed as edgecase_stream_2.
|
||||
stream_data.add_sub(amp_stream);
|
||||
|
||||
markdown.initialize(example_realm_filters, markdown_config.get_helpers());
|
||||
markdown.initialize(example_realm_linkifiers, markdown_config.get_helpers());
|
||||
|
||||
function test(label, f) {
|
||||
run_test(label, (override) => {
|
||||
page_params.realm_users = [];
|
||||
markdown.update_linkifier_rules(example_realm_filters);
|
||||
markdown.update_linkifier_rules(example_realm_linkifiers);
|
||||
f(override);
|
||||
});
|
||||
}
|
||||
|
@ -713,13 +722,28 @@ test("backend_only_linkifiers", () => {
|
|||
test("python_to_js_linkifier", () => {
|
||||
// The only way to reach python_to_js_linkifier is indirectly, hence the call
|
||||
// to update_linkifier_rules.
|
||||
markdown.update_linkifier_rules([["/a(?im)a/g"], ["/a(?L)a/g"]]);
|
||||
markdown.update_linkifier_rules([
|
||||
{
|
||||
pattern: "/a(?im)a/g",
|
||||
url_format: "http://example1.example.com",
|
||||
id: 10,
|
||||
},
|
||||
{
|
||||
pattern: "/a(?L)a/g",
|
||||
url_format: "http://example2.example.com",
|
||||
id: 20,
|
||||
},
|
||||
]);
|
||||
let actual_value = marked.InlineLexer.rules.zulip.linkifiers;
|
||||
let expected_value = [/\/aa\/g(?!\w)/gim, /\/aa\/g(?!\w)/g];
|
||||
assert.deepEqual(actual_value, expected_value);
|
||||
// Test case with multiple replacements.
|
||||
markdown.update_linkifier_rules([
|
||||
["#cf(?P<contest>\\d+)(?P<problem>[A-Z][\\dA-Z]*)", "http://google.com"],
|
||||
{
|
||||
pattern: "#cf(?P<contest>\\d+)(?P<problem>[A-Z][\\dA-Z]*)",
|
||||
url_format: "http://example3.example.com",
|
||||
id: 30,
|
||||
},
|
||||
]);
|
||||
actual_value = marked.InlineLexer.rules.zulip.linkifiers;
|
||||
expected_value = [/#cf(\d+)([A-Z][\dA-Z]*)(?!\w)/g];
|
||||
|
@ -729,7 +753,13 @@ test("python_to_js_linkifier", () => {
|
|||
"error",
|
||||
"python_to_js_linkifier: Invalid regular expression: /!@#@(!#&((!&(@#((?!\\w)/: Unterminated group",
|
||||
);
|
||||
markdown.update_linkifier_rules([["!@#@(!#&((!&(@#(", "http://google.com"]]);
|
||||
markdown.update_linkifier_rules([
|
||||
{
|
||||
pattern: "!@#@(!#&((!&(@#(",
|
||||
url_format: "http://example4.example.com",
|
||||
id: 40,
|
||||
},
|
||||
]);
|
||||
actual_value = marked.InlineLexer.rules.zulip.linkifiers;
|
||||
expected_value = [];
|
||||
assert.deepEqual(actual_value, expected_value);
|
||||
|
|
|
@ -89,7 +89,7 @@ export function contains_backend_only_syntax(content) {
|
|||
// then don't render it locally. It is workaround for the fact that
|
||||
// javascript regex doesn't support lookbehind.
|
||||
const false_linkifier_match = linkifier_list.find((re) => {
|
||||
const pattern = /[^\s"'(,:<]/.source + re[0].source + /(?!\w)/.source;
|
||||
const pattern = /[^\s"'(,:<]/.source + re.pattern.source + /(?!\w)/.source;
|
||||
const regex = new RegExp(pattern);
|
||||
return regex.test(content);
|
||||
});
|
||||
|
@ -225,8 +225,8 @@ export function add_topic_links(message) {
|
|||
const links = [];
|
||||
|
||||
for (const linkifier of linkifier_list) {
|
||||
const pattern = linkifier[0];
|
||||
const url = linkifier[1];
|
||||
const pattern = linkifier.pattern;
|
||||
const url = linkifier.url_format;
|
||||
let match;
|
||||
while ((match = pattern.exec(topic)) !== null) {
|
||||
let link_url = url;
|
||||
|
@ -451,15 +451,18 @@ export function update_linkifier_rules(linkifiers) {
|
|||
|
||||
const marked_rules = [];
|
||||
|
||||
for (const [pattern, url] of linkifiers) {
|
||||
const [regex, final_url] = python_to_js_linkifier(pattern, url);
|
||||
for (const linkifier of linkifiers) {
|
||||
const [regex, final_url] = python_to_js_linkifier(linkifier.pattern, linkifier.url_format);
|
||||
if (!regex) {
|
||||
// Skip any linkifiers that could not be converted
|
||||
continue;
|
||||
}
|
||||
|
||||
linkifier_map.set(regex, final_url);
|
||||
linkifier_list.push([regex, final_url]);
|
||||
linkifier_list.push({
|
||||
pattern: regex,
|
||||
url_format: final_url,
|
||||
});
|
||||
marked_rules.push(regex);
|
||||
}
|
||||
|
||||
|
|
|
@ -342,10 +342,10 @@ export function dispatch_normal_event(event) {
|
|||
composebox_typeahead.update_emoji_data();
|
||||
break;
|
||||
|
||||
case "realm_filters":
|
||||
page_params.realm_filters = event.realm_filters;
|
||||
markdown.update_linkifier_rules(page_params.realm_filters);
|
||||
settings_linkifiers.populate_linkifiers(page_params.realm_filters);
|
||||
case "realm_linkifiers":
|
||||
page_params.realm_linkifiers = event.realm_linkifiers;
|
||||
markdown.update_linkifier_rules(page_params.realm_linkifiers);
|
||||
settings_linkifiers.populate_linkifiers(page_params.realm_linkifiers);
|
||||
break;
|
||||
|
||||
case "realm_domains":
|
||||
|
|
|
@ -23,21 +23,21 @@ export function maybe_disable_widgets() {
|
|||
}
|
||||
}
|
||||
|
||||
function compare_by_index(a, b, i) {
|
||||
if (a[i] > b[i]) {
|
||||
function compare_values(x, y) {
|
||||
if (x > y) {
|
||||
return 1;
|
||||
} else if (a[i] === b[i]) {
|
||||
} else if (x === y) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function sort_pattern(a, b) {
|
||||
return compare_by_index(a, b, 0);
|
||||
return compare_values(a.pattern, b.pattern);
|
||||
}
|
||||
|
||||
function sort_url(a, b) {
|
||||
return compare_by_index(a, b, 1);
|
||||
return compare_values(a.url_format, b.url_format);
|
||||
}
|
||||
|
||||
export function populate_linkifiers(linkifiers_data) {
|
||||
|
@ -51,9 +51,9 @@ export function populate_linkifiers(linkifiers_data) {
|
|||
modifier(linkifier) {
|
||||
return render_admin_linkifier_list({
|
||||
linkifier: {
|
||||
pattern: linkifier[0],
|
||||
url_format_string: linkifier[1],
|
||||
id: linkifier[2],
|
||||
pattern: linkifier.pattern,
|
||||
url_format_string: linkifier.url_format,
|
||||
id: linkifier.id,
|
||||
},
|
||||
can_modify: page_params.is_admin,
|
||||
});
|
||||
|
@ -62,7 +62,8 @@ export function populate_linkifiers(linkifiers_data) {
|
|||
element: linkifiers_table.closest(".settings-section").find(".search"),
|
||||
predicate(item, value) {
|
||||
return (
|
||||
item[0].toLowerCase().includes(value) || item[1].toLowerCase().includes(value)
|
||||
item.pattern.toLowerCase().includes(value) ||
|
||||
item.url_format.toLowerCase().includes(value)
|
||||
);
|
||||
},
|
||||
onupdate() {
|
||||
|
@ -88,7 +89,7 @@ export function build_page() {
|
|||
meta.loaded = true;
|
||||
|
||||
// Populate linkifiers table
|
||||
populate_linkifiers(page_params.realm_filters);
|
||||
populate_linkifiers(page_params.realm_linkifiers);
|
||||
|
||||
$(".admin_linkifiers_table").on("click", ".delete", function (e) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -496,7 +496,7 @@ export function initialize_everything() {
|
|||
realm_emoji: emoji_params.realm_emoji,
|
||||
emoji_codes: generated_emoji_codes,
|
||||
});
|
||||
markdown.initialize(page_params.realm_filters, markdown_config.get_helpers());
|
||||
markdown.initialize(page_params.realm_linkifiers, markdown_config.get_helpers());
|
||||
compose.initialize();
|
||||
composebox_typeahead.initialize(); // Must happen after compose.initialize()
|
||||
search.initialize();
|
||||
|
|
|
@ -10,6 +10,23 @@ below features are supported.
|
|||
|
||||
## Changes in Zulip 4.0
|
||||
|
||||
**Feature level 54**
|
||||
|
||||
* `GET /realm/filters` has been removed and replace with [`GET
|
||||
/realm/linkifiers`](/api/get-linkifiers) which returns the data in a
|
||||
cleaner dictionary format.
|
||||
* [`GET /events`](/api/get-events): Introduced new event type
|
||||
`realm_linkifiers`. The previous `realm_filters` event type is
|
||||
still supported for backwards compatibility, but will be removed in
|
||||
a future release.
|
||||
* [`POST /register`](/api/register-queue): The response now supports a
|
||||
`realm_linkifiers` event type, containing the same data as the
|
||||
legacy `realm_filters` key, with a more extensible object
|
||||
format. The previous `realm_filters` event type is still supported
|
||||
for backwards compatibility, but will be removed in a future
|
||||
release. The legacy `realm_filters` key is deprecated but remains
|
||||
available for backwards compatibility.
|
||||
|
||||
**Feature level 53**
|
||||
|
||||
* [`POST /register`](/api/register-queue): Added `max_topic_length`
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
# Get linkifiers
|
||||
|
||||
{generate_api_description(/realm/filters:get)}
|
||||
{generate_api_description(/realm/linkifiers:get)}
|
||||
|
||||
## Usage examples
|
||||
|
||||
{start_tabs}
|
||||
{tab|python}
|
||||
|
||||
{generate_code_example(python)|/realm/filters:get|example}
|
||||
{generate_code_example(python)|/realm/linkifiers:get|example}
|
||||
|
||||
{tab|curl}
|
||||
|
||||
{generate_code_example(curl)|/realm/filters:get|example}
|
||||
{generate_code_example(curl)|/realm/linkifiers:get|example}
|
||||
|
||||
{end_tabs}
|
||||
|
||||
## Parameters
|
||||
|
||||
{generate_api_arguments_table|zulip.yaml|/realm/filters:get}
|
||||
{generate_api_arguments_table|zulip.yaml|/realm/linkifiers:get}
|
||||
|
||||
## Response
|
||||
|
||||
#### Return values
|
||||
|
||||
{generate_return_values_table|zulip.yaml|/realm/filters:get}
|
||||
{generate_return_values_table|zulip.yaml|/realm/linkifiers:get}
|
||||
|
||||
#### Example response
|
||||
|
||||
A typical successful JSON response may look like:
|
||||
|
||||
{generate_code_example|/realm/filters:get|fixture(200)}
|
||||
{generate_code_example|/realm/linkifiers:get|fixture(200)}
|
||||
|
|
|
@ -30,7 +30,7 @@ DESKTOP_WARNING_VERSION = "5.2.0"
|
|||
#
|
||||
# Changes should be accompanied by documentation explaining what the
|
||||
# new level means in templates/zerver/api/changelog.md.
|
||||
API_FEATURE_LEVEL = 53
|
||||
API_FEATURE_LEVEL = 54
|
||||
|
||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||
# only when going from an old version of the code to a newer version. Bump
|
||||
|
|
|
@ -236,6 +236,7 @@ from zerver.models import (
|
|||
get_user_by_id_in_realm_including_cross_realm,
|
||||
get_user_profile_by_id,
|
||||
is_cross_realm_bot_email,
|
||||
linkifiers_for_realm,
|
||||
query_for_ids,
|
||||
realm_filters_for_realm,
|
||||
validate_attachment_request,
|
||||
|
@ -6593,6 +6594,13 @@ def do_mark_hotspot_as_read(user: UserProfile, hotspot: str) -> None:
|
|||
|
||||
|
||||
def notify_linkifiers(realm: Realm) -> None:
|
||||
realm_linkifiers = linkifiers_for_realm(realm.id)
|
||||
event = dict(type="realm_linkifiers", realm_linkifiers=realm_linkifiers)
|
||||
send_event(realm, event, active_user_ids(realm.id))
|
||||
|
||||
# Below is code for backwards compatibility. The now deprecated
|
||||
# "realm_filters" event-type is used by older clients, and uses
|
||||
# tuples.
|
||||
realm_filters = realm_filters_for_realm(realm.id)
|
||||
event = dict(type="realm_filters", realm_filters=realm_filters)
|
||||
send_event(realm, event, active_user_ids(realm.id))
|
||||
|
|
|
@ -781,9 +781,26 @@ def check_realm_export(
|
|||
assert has_failed_timestamp == (export["failed_timestamp"] is not None)
|
||||
|
||||
|
||||
# This type, like other instances of TupleType, is a legacy feature of
|
||||
# a very old Zulip API; we plan to replace it with an object as those
|
||||
# are more extensible.
|
||||
realm_linkifier_type = DictType(
|
||||
required_keys=[
|
||||
("pattern", str),
|
||||
("url_format", str),
|
||||
("id", int),
|
||||
]
|
||||
)
|
||||
|
||||
realm_linkifiers_event = event_dict_type(
|
||||
[
|
||||
("type", Equals("realm_linkifiers")),
|
||||
("realm_linkifiers", ListType(realm_linkifier_type)),
|
||||
]
|
||||
)
|
||||
check_realm_linkifiers = make_checker(realm_linkifiers_event)
|
||||
|
||||
|
||||
# This is a legacy event type to ensure backwards compatibility
|
||||
# for old clients. Newer clients should handle only the
|
||||
# "realm_linkifiers" event above.
|
||||
realm_filter_type = TupleType(
|
||||
[
|
||||
# we should make this an object
|
||||
|
|
|
@ -62,6 +62,7 @@ from zerver.models import (
|
|||
get_default_stream_groups,
|
||||
get_realm_domains,
|
||||
get_realm_playgrounds,
|
||||
linkifiers_for_realm,
|
||||
realm_filters_for_realm,
|
||||
)
|
||||
from zerver.tornado.django_api import get_user_events, request_event_queue
|
||||
|
@ -263,6 +264,10 @@ def fetch_initial_state_data(
|
|||
if want("realm_emoji"):
|
||||
state["realm_emoji"] = realm.get_emoji()
|
||||
|
||||
if want("realm_linkifiers"):
|
||||
state["realm_linkifiers"] = linkifiers_for_realm(realm.id)
|
||||
|
||||
# Backwards compatibility code.
|
||||
if want("realm_filters"):
|
||||
state["realm_filters"] = realm_filters_for_realm(realm.id)
|
||||
|
||||
|
@ -993,6 +998,8 @@ def apply_event(
|
|||
state["muted_users"] = event["muted_users"]
|
||||
elif event["type"] == "realm_filters":
|
||||
state["realm_filters"] = event["realm_filters"]
|
||||
elif event["type"] == "realm_linkifiers":
|
||||
state["realm_linkifiers"] = event["realm_linkifiers"]
|
||||
elif event["type"] == "realm_playgrounds":
|
||||
state["realm_playgrounds"] = event["realm_playgrounds"]
|
||||
elif event["type"] == "update_display_settings":
|
||||
|
|
|
@ -308,15 +308,18 @@ def get_subscription_status(client: Client) -> None:
|
|||
)
|
||||
|
||||
|
||||
@openapi_test_function("/realm/filters:get")
|
||||
def get_realm_filters(client: Client) -> None:
|
||||
@openapi_test_function("/realm/linkifiers:get")
|
||||
def get_realm_linkifiers(client: Client) -> None:
|
||||
|
||||
# {code_example|start}
|
||||
# Fetch all the filters in this organization
|
||||
result = client.get_realm_filters()
|
||||
result = client.call_endpoint(
|
||||
url="/realm/linkifiers",
|
||||
method="GET",
|
||||
)
|
||||
# {code_example|end}
|
||||
|
||||
validate_against_openapi_schema(result, "/realm/filters", "get", "200")
|
||||
validate_against_openapi_schema(result, "/realm/linkifiers", "get", "200")
|
||||
|
||||
|
||||
@openapi_test_function("/realm/profile_fields:get")
|
||||
|
@ -1459,7 +1462,7 @@ def test_queues(client: Client) -> None:
|
|||
|
||||
def test_server_organizations(client: Client) -> None:
|
||||
|
||||
get_realm_filters(client)
|
||||
get_realm_linkifiers(client)
|
||||
add_realm_filter(client)
|
||||
add_realm_playground(client)
|
||||
get_server_settings(client)
|
||||
|
|
|
@ -2377,6 +2377,69 @@ paths:
|
|||
|
||||
Processing this event is important to doing Markdown local echo
|
||||
correctly.
|
||||
|
||||
**Changes**: New in Zulip 4.0 (feature level 54), replacing the
|
||||
previous `realm_filters` event type, which is still sent for
|
||||
backwards compatibility reasons.
|
||||
|
||||
Clients should migrate to requesting and processing the
|
||||
`realm_linkifiers` event type when possible, since we plan to remove
|
||||
the legacy `realm_filters` logic entirely in a future release.
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/EventIdSchema"
|
||||
type:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/EventTypeSchema"
|
||||
- enum:
|
||||
- realm_linkifiers
|
||||
realm_linkifiers:
|
||||
type: array
|
||||
description: |
|
||||
Array of dictionaries where each dictionary contains details about
|
||||
a single realm linkifier.
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
pattern:
|
||||
type: string
|
||||
description: |
|
||||
The string regex pattern which represents the pattern that
|
||||
should be linkified by this linkifier.
|
||||
url_format:
|
||||
type: string
|
||||
description: |
|
||||
The URL format string to be used for linkifying matches.
|
||||
|
||||
id:
|
||||
type: integer
|
||||
description: |
|
||||
The ID of the linkifier.
|
||||
example:
|
||||
{
|
||||
"type": "realm_linkifiers",
|
||||
"realm_linkifiers":
|
||||
[
|
||||
{
|
||||
"pattern": "#(?P<id>[123])",
|
||||
"url_format": "https://realm.com/my_realm_filter/%(id)s",
|
||||
"id": 1,
|
||||
},
|
||||
],
|
||||
"id": 0,
|
||||
}
|
||||
- type: object
|
||||
additionalProperties: false
|
||||
deprecated: true
|
||||
description: |
|
||||
Legacy event type. Sent to all users in a Zulip organization
|
||||
when the set of configured [linkifiers](/help/add-a-custom-linkifier)
|
||||
for the organization has changed.
|
||||
|
||||
**Changes**: Deprecated in Zulip 4.0 (feature level 54), replaced by
|
||||
the `realm_linkifiers` event type, which has a clearer name and format,
|
||||
instead.
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/EventIdSchema"
|
||||
|
@ -6485,7 +6548,7 @@ paths:
|
|||
"msg": "Cannot deactivate the only organization owner",
|
||||
"result": "error",
|
||||
}
|
||||
/realm/filters:
|
||||
/realm/linkifiers:
|
||||
get:
|
||||
operationId: get_linkifiers
|
||||
tags: ["server_and_organizations"]
|
||||
|
@ -6495,7 +6558,11 @@ paths:
|
|||
expression patterns that are automatically linkified when they appear
|
||||
in messages and topics.
|
||||
|
||||
`GET {{ api_url }}/v1/realm/filters`
|
||||
`GET {{ api_url }}/v1/realm/linkifiers`
|
||||
|
||||
**Changes**: New in Zulip 4.0 (feature level 54). On older versions,
|
||||
a similar `GET /realm/filters` endpoint was available with each entry in
|
||||
a `[pattern, url_format, id]` tuple format.
|
||||
responses:
|
||||
"200":
|
||||
description: Success.
|
||||
|
@ -6508,35 +6575,41 @@ paths:
|
|||
properties:
|
||||
result: {}
|
||||
msg: {}
|
||||
filters:
|
||||
linkifiers:
|
||||
type: array
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: integer
|
||||
description: |
|
||||
An array of tuples, each representing one of
|
||||
the linkifiers set up in the
|
||||
organization. Each of these tuples contain the
|
||||
pattern, the formatted URL and the filter's
|
||||
ID, in that order. See the [Create
|
||||
linkifiers](/api/add-linkifier) article for
|
||||
details on what each field means.
|
||||
An array of objects, where each object describes a linkifier.
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
pattern:
|
||||
type: string
|
||||
description: |
|
||||
The string regex pattern which represents the pattern that
|
||||
should be linkified by this linkifier.
|
||||
url_format:
|
||||
type: string
|
||||
description: |
|
||||
The URL format string to be used for linkifying matches.
|
||||
id:
|
||||
type: integer
|
||||
description: |
|
||||
The ID of the linkifier.
|
||||
example:
|
||||
{
|
||||
"msg": "",
|
||||
"filters":
|
||||
"linkifiers":
|
||||
[
|
||||
[
|
||||
"#(?P<id>[0-9]+)",
|
||||
"https://github.com/zulip/zulip/issues/%(id)s",
|
||||
1,
|
||||
],
|
||||
{
|
||||
"pattern": "#(?P<id>[0-9]+)",
|
||||
"url_format": "https://github.com/zulip/zulip/issues/%(id)s",
|
||||
"id": 1,
|
||||
},
|
||||
],
|
||||
"result": "success",
|
||||
}
|
||||
/realm/filters:
|
||||
post:
|
||||
operationId: add_linkifier
|
||||
tags: ["server_and_organizations"]
|
||||
|
@ -7009,8 +7082,37 @@ paths:
|
|||
- type: array
|
||||
items:
|
||||
type: integer
|
||||
realm_linkifiers:
|
||||
type: array
|
||||
description: |
|
||||
Present if `realm_linkifiers` is present in `fetch_event_types`.
|
||||
|
||||
Array of objects where each object describes a single
|
||||
[linkifier](/help/add-a-custom-linkifier).
|
||||
|
||||
**Changes**: New in Zulip 4.0 (feature level 54). Clients can
|
||||
access these data on older server versions via the previous
|
||||
`realm_filters` key.
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
pattern:
|
||||
type: string
|
||||
description: |
|
||||
The string regex pattern which represents the pattern that
|
||||
should be linkified on matching.
|
||||
url_format:
|
||||
type: string
|
||||
description: |
|
||||
The URL with which the pattern matching string should be linkified.
|
||||
id:
|
||||
type: integer
|
||||
description: |
|
||||
The ID of the linkifier.
|
||||
realm_filters:
|
||||
type: array
|
||||
deprecated: true
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
|
@ -7018,16 +7120,20 @@ paths:
|
|||
- type: integer
|
||||
- type: string
|
||||
description: |
|
||||
Present if `realm_filters` is present in `fetch_event_types`.
|
||||
Legacy property for linkifiers. Present if `realm_filters` is
|
||||
present in `fetch_event_types`.
|
||||
|
||||
An array of tuples (fixed-length arrays) where each tuple describes
|
||||
a single realm filter ([linkifier](/help/add-a-custom-linkifier).
|
||||
a single [linkifier](/help/add-a-custom-linkifier).
|
||||
The first element of the tuple is a string regex pattern which represents
|
||||
the pattern that should be linkified on matching.
|
||||
|
||||
The second element is the URL with which the
|
||||
pattern matching string should be linkified with and the third element
|
||||
is the id of the realm filter.
|
||||
|
||||
**Changes**: Deprecated in Zulip 4.0 (feature level 54), replaced by
|
||||
the `realm_linkifiers` key instead.
|
||||
realm_playgrounds:
|
||||
type: array
|
||||
items:
|
||||
|
|
|
@ -871,6 +871,7 @@ class FetchQueriesTest(ZulipTestCase):
|
|||
realm_incoming_webhook_bots=0,
|
||||
realm_emoji=1,
|
||||
realm_filters=1,
|
||||
realm_linkifiers=1,
|
||||
realm_playgrounds=1,
|
||||
realm_user=3,
|
||||
realm_user_groups=2,
|
||||
|
|
|
@ -128,6 +128,7 @@ from zerver.lib.event_schema import (
|
|||
check_realm_emoji_update,
|
||||
check_realm_export,
|
||||
check_realm_filters,
|
||||
check_realm_linkifiers,
|
||||
check_realm_playgrounds,
|
||||
check_realm_update,
|
||||
check_realm_update_dict,
|
||||
|
@ -1349,13 +1350,18 @@ class NormalActionsTest(BaseAction):
|
|||
regex = "#(?P<id>[123])"
|
||||
url = "https://realm.com/my_realm_filter/%(id)s"
|
||||
|
||||
events = self.verify_action(lambda: do_add_linkifier(self.user_profile.realm, regex, url))
|
||||
check_realm_filters("events[0]", events[0])
|
||||
events = self.verify_action(
|
||||
lambda: do_add_linkifier(self.user_profile.realm, regex, url), num_events=2
|
||||
)
|
||||
check_realm_linkifiers("events[0]", events[0])
|
||||
check_realm_filters("events[1]", events[1])
|
||||
|
||||
events = self.verify_action(
|
||||
lambda: do_remove_linkifier(self.user_profile.realm, "#(?P<id>[123])")
|
||||
lambda: do_remove_linkifier(self.user_profile.realm, "#(?P<id>[123])"),
|
||||
num_events=2,
|
||||
)
|
||||
check_realm_filters("events[0]", events[0])
|
||||
check_realm_linkifiers("events[0]", events[0])
|
||||
check_realm_filters("events[1]", events[1])
|
||||
|
||||
def test_realm_domain_events(self) -> None:
|
||||
events = self.verify_action(
|
||||
|
|
|
@ -167,6 +167,7 @@ class HomeTest(ZulipTestCase):
|
|||
"realm_invite_to_realm_policy",
|
||||
"realm_invite_to_stream_policy",
|
||||
"realm_is_zephyr_mirror_realm",
|
||||
"realm_linkifiers",
|
||||
"realm_logo_source",
|
||||
"realm_logo_url",
|
||||
"realm_mandatory_topics",
|
||||
|
|
|
@ -10,10 +10,10 @@ class RealmFilterTest(ZulipTestCase):
|
|||
self.login("iago")
|
||||
realm = get_realm("zulip")
|
||||
do_add_linkifier(realm, "#(?P<id>[123])", "https://realm.com/my_realm_filter/%(id)s")
|
||||
result = self.client_get("/json/realm/filters")
|
||||
result = self.client_get("/json/realm/linkifiers")
|
||||
self.assert_json_success(result)
|
||||
self.assertEqual(200, result.status_code)
|
||||
self.assertEqual(len(result.json()["filters"]), 1)
|
||||
self.assertEqual(len(result.json()["linkifiers"]), 1)
|
||||
|
||||
def test_create(self) -> None:
|
||||
self.login("iago")
|
||||
|
|
|
@ -6,13 +6,13 @@ from zerver.decorator import require_realm_admin
|
|||
from zerver.lib.actions import do_add_linkifier, do_remove_linkifier
|
||||
from zerver.lib.request import REQ, has_request_variables
|
||||
from zerver.lib.response import json_error, json_success
|
||||
from zerver.models import RealmFilter, UserProfile, realm_filters_for_realm
|
||||
from zerver.models import RealmFilter, UserProfile, linkifiers_for_realm
|
||||
|
||||
|
||||
# Custom realm linkifiers
|
||||
def list_linkifiers(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
||||
filters = realm_filters_for_realm(user_profile.realm_id)
|
||||
return json_success({"filters": filters})
|
||||
linkifiers = linkifiers_for_realm(user_profile.realm_id)
|
||||
return json_success({"linkifiers": linkifiers})
|
||||
|
||||
|
||||
@require_realm_admin
|
||||
|
|
|
@ -262,8 +262,9 @@ v1_api_and_json_patterns = [
|
|||
rest_path("realm/icon", POST=upload_icon, DELETE=delete_icon_backend, GET=get_icon_backend),
|
||||
# realm/logo -> zerver.views.realm_logo
|
||||
rest_path("realm/logo", POST=upload_logo, DELETE=delete_logo_backend, GET=get_logo_backend),
|
||||
# realm/filters -> zerver.views.realm_linkifiers
|
||||
rest_path("realm/filters", GET=list_linkifiers, POST=create_linkifier),
|
||||
# realm/filters and realm/linkifiers -> zerver.views.realm_linkifiers
|
||||
rest_path("realm/linkifiers", GET=list_linkifiers),
|
||||
rest_path("realm/filters", POST=create_linkifier),
|
||||
rest_path("realm/filters/<int:filter_id>", DELETE=delete_linkifier),
|
||||
# realm/playgrounds -> zerver.views.realm_playgrounds
|
||||
rest_path("realm/playgrounds", POST=add_realm_playground),
|
||||
|
|
Loading…
Reference in New Issue