From ec188a9509ccaf7c7f921c62cc40800bf7f6ff04 Mon Sep 17 00:00:00 2001 From: Alexandre Fournier Date: Thu, 6 Jul 2023 09:16:13 -0400 Subject: [PATCH 1/4] outcalls: add label and autogenerate name --- integration_tests/suite/base/test_outcalls.py | 20 ++++++++--------- .../suite/helpers/helpers/outcall.py | 20 ++++++++--------- wazo_confd/plugins/outcall/api.yml | 6 ++++- wazo_confd/plugins/outcall/plugin.py | 8 +++++-- wazo_confd/plugins/outcall/resource.py | 19 ++++++++++++++-- wazo_confd/plugins/outcall/schema.py | 22 ++++++++++++++++--- wazo_confd/plugins/outcall/validator.py | 4 +--- 7 files changed, 67 insertions(+), 32 deletions(-) diff --git a/integration_tests/suite/base/test_outcalls.py b/integration_tests/suite/base/test_outcalls.py index 1a8ce73d7..3e092f3fb 100644 --- a/integration_tests/suite/base/test_outcalls.py +++ b/integration_tests/suite/base/test_outcalls.py @@ -1,4 +1,4 @@ -# Copyright 2016-2022 The Wazo Authors (see the AUTHORS file) +# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file) # SPDX-License-Identifier: GPL-3.0-or-later from hamcrest import ( @@ -53,6 +53,12 @@ def error_checks(url): yield s.check_bogus_field_returns_error, url, 'name', 1234 yield s.check_bogus_field_returns_error, url, 'name', [] yield s.check_bogus_field_returns_error, url, 'name', {} + yield s.check_bogus_field_returns_error, url, 'name', 'name' + yield s.check_bogus_field_returns_error, url, 'label', 123 + yield s.check_bogus_field_returns_error, url, 'label', None + yield s.check_bogus_field_returns_error, url, 'label', True + yield s.check_bogus_field_returns_error, url, 'label', {} + yield s.check_bogus_field_returns_error, url, 'label', [] yield s.check_bogus_field_returns_error, url, 'internal_caller_id', 1234 yield s.check_bogus_field_returns_error, url, 'internal_caller_id', 'invalid' yield s.check_bogus_field_returns_error, url, 'internal_caller_id', None @@ -69,20 +75,12 @@ def error_checks(url): yield s.check_bogus_field_returns_error, url, 'enabled', [] yield s.check_bogus_field_returns_error, url, 'enabled', {} - for check in unique_error_checks(url): - yield check - - -@fixtures.outcall(name='unique') -def unique_error_checks(url, outcall): - yield s.check_bogus_field_returns_error, url, 'name', outcall['name'] - -@fixtures.outcall(description='search') +@fixtures.outcall(label='search', description='search') @fixtures.outcall(description='hidden') def test_search(outcall, hidden): url = confd.outcalls - searches = {'description': 'search'} + searches = {'label': 'search', 'description': 'search'} for field, term in searches.items(): yield check_search, url, outcall, hidden, field, term diff --git a/integration_tests/suite/helpers/helpers/outcall.py b/integration_tests/suite/helpers/helpers/outcall.py index e46e5c5d2..21730924d 100644 --- a/integration_tests/suite/helpers/helpers/outcall.py +++ b/integration_tests/suite/helpers/helpers/outcall.py @@ -1,4 +1,4 @@ -# Copyright 2016-2021 The Wazo Authors (see the AUTHORS file) +# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file) # SPDX-License-Identifier: GPL-3.0-or-later import random @@ -9,7 +9,7 @@ def generate_outcall(**parameters): - parameters.setdefault('name', generate_name()) + parameters.setdefault('label', generate_label()) parameters.setdefault('context', CONTEXT) return add_outcall(**parameters) @@ -25,14 +25,14 @@ def delete_outcall(outcall_id, check=False, **params): response.assert_ok() -def generate_name(): +def generate_label(): response = confd.outcalls.get() - names = set(d['name'] for d in response.items) - return _random_name(names) + labels = set(d['label'] for d in response.items) + return _random_label(labels) -def _random_name(names): - name = ''.join(random.choice(string.ascii_letters) for _ in range(10)) - if name in names: - return _random_name(names) - return name +def _random_label(labels): + label = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + while label in labels: + label = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + return label diff --git a/wazo_confd/plugins/outcall/api.yml b/wazo_confd/plugins/outcall/api.yml index 169d33de4..2a7eb4a53 100644 --- a/wazo_confd/plugins/outcall/api.yml +++ b/wazo_confd/plugins/outcall/api.yml @@ -133,7 +133,7 @@ definitions: - $ref: '#/definitions/OutcallRelationSchedules' - $ref: '#/definitions/OutcallRelationCallPermissions' - required: - - name + - label OutcallRelationBase: properties: id: @@ -143,6 +143,10 @@ definitions: name: type: string description: The name of the outcall + readOnly: true + label: + type: string + description: The label of the outcall OutcallRelationTrunks: properties: trunks: diff --git a/wazo_confd/plugins/outcall/plugin.py b/wazo_confd/plugins/outcall/plugin.py index d9a13e7a1..24994ce1e 100644 --- a/wazo_confd/plugins/outcall/plugin.py +++ b/wazo_confd/plugins/outcall/plugin.py @@ -1,6 +1,8 @@ -# Copyright 2016-2019 The Wazo Authors (see the AUTHORS file) +# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file) # SPDX-License-Identifier: GPL-3.0-or-later +from xivo_dao.resources.tenant import dao as tenant_dao + from .resource import OutcallItem, OutcallList from .service import build_service @@ -10,7 +12,9 @@ def load(self, dependencies): api = dependencies['api'] service = build_service() - api.add_resource(OutcallList, '/outcalls', resource_class_args=(service,)) + api.add_resource( + OutcallList, '/outcalls', resource_class_args=(tenant_dao, service) + ) api.add_resource( OutcallItem, diff --git a/wazo_confd/plugins/outcall/resource.py b/wazo_confd/plugins/outcall/resource.py index 64dc5692d..6ab479fe6 100644 --- a/wazo_confd/plugins/outcall/resource.py +++ b/wazo_confd/plugins/outcall/resource.py @@ -1,7 +1,7 @@ # Copyright 2016-2023 The Wazo Authors (see the AUTHORS file) # SPDX-License-Identifier: GPL-3.0-or-later -from flask import url_for +from flask import request, url_for from xivo_dao.alchemy.outcall import Outcall @@ -14,13 +14,28 @@ class OutcallList(ListResource): model = Outcall schema = OutcallSchema + outcall_name_fmt = 'outcall-{tenant_slug}-{outcall_id}' + + def __init__(self, tenant_dao, *args, **kwargs): + super().__init__(*args, **kwargs) + self._tenant_dao = tenant_dao def build_headers(self, outcall): return {'Location': url_for('outcalls', id=outcall.id, _external=True)} @required_acl('confd.outcalls.create') def post(self): - return super().post() + form = self.schema().load(request.get_json()) + form = self.add_tenant_to_form(form) + + tenant = self._tenant_dao.get(form['tenant_uuid']) + form['name'] = self.outcall_name_fmt.format( + tenant_slug=tenant.slug, + outcall_id=form['id'], + ) + model = self.model(**form) + model = self.service.create(model) + return self.schema().dump(model), 201, self.build_headers(model) @required_acl('confd.outcalls.read') def get(self): diff --git a/wazo_confd/plugins/outcall/schema.py b/wazo_confd/plugins/outcall/schema.py index 3562fd427..ed9e365fe 100644 --- a/wazo_confd/plugins/outcall/schema.py +++ b/wazo_confd/plugins/outcall/schema.py @@ -1,17 +1,21 @@ -# Copyright 2016-2022 The Wazo Authors (see the AUTHORS file) +# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file) # SPDX-License-Identifier: GPL-3.0-or-later +import logging -from marshmallow import fields, post_dump +from marshmallow import fields, post_dump, pre_load from marshmallow.validate import Length, Range from wazo_confd.helpers.mallow import BaseSchema, StrictBoolean, Link, ListLink, Nested +logger = logging.getLogger(__name__) + class OutcallSchema(BaseSchema): id = fields.Integer(dump_only=True) tenant_uuid = fields.String(dump_only=True) - name = fields.String(validate=Length(max=128), required=True) + name = fields.String(dump_only=True) + label = fields.String(validate=Length(max=128), required=True) internal_caller_id = StrictBoolean() preprocess_subroutine = fields.String(validate=Length(max=39), allow_none=True) ring_time = fields.Integer(validate=Range(min=0), allow_none=True) @@ -40,6 +44,18 @@ class OutcallSchema(BaseSchema): dump_only=True, ) + # DEPRECATED 23.10 + @pre_load + def copy_name_to_label(self, data, **kwargs): + if 'label' in data: + return data + if 'name' in data: + logger.warning( + 'The "name" field of outcall is deprecated. Use "label" instead' + ) + data['label'] = data['name'] + return data + class DialPatternSchema(BaseSchema): external_prefix = fields.String() diff --git a/wazo_confd/plugins/outcall/validator.py b/wazo_confd/plugins/outcall/validator.py index 89c9a6a74..124d231cf 100644 --- a/wazo_confd/plugins/outcall/validator.py +++ b/wazo_confd/plugins/outcall/validator.py @@ -1,11 +1,10 @@ -# Copyright 2016-2020 The Wazo Authors (see the AUTHORS file) +# Copyright 2016-2023 The Wazo Authors (see the AUTHORS file) # SPDX-License-Identifier: GPL-3.0-or-later from xivo_dao.resources.outcall import dao as outcall_dao from wazo_confd.helpers.validator import ( UniqueField, - UniqueFieldChanged, ValidationGroup, ) @@ -15,5 +14,4 @@ def build_validator(): create=[ UniqueField('name', lambda name: outcall_dao.find_by(name=name), 'Outcall') ], - edit=[UniqueFieldChanged('name', outcall_dao.find_by, 'Outcall')], ) From 5bc51884ba047710ac2fd839fadd4073db568cd8 Mon Sep 17 00:00:00 2001 From: Alexandre Fournier Date: Thu, 6 Jul 2023 15:11:03 -0400 Subject: [PATCH 2/4] outcall: generate name using a fake UUID Why: outcalls do not use UUIDs yet --- wazo_confd/plugins/outcall/resource.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wazo_confd/plugins/outcall/resource.py b/wazo_confd/plugins/outcall/resource.py index 6ab479fe6..942f8902e 100644 --- a/wazo_confd/plugins/outcall/resource.py +++ b/wazo_confd/plugins/outcall/resource.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from flask import request, url_for +from uuid import uuid4 from xivo_dao.alchemy.outcall import Outcall @@ -14,7 +15,7 @@ class OutcallList(ListResource): model = Outcall schema = OutcallSchema - outcall_name_fmt = 'outcall-{tenant_slug}-{outcall_id}' + outcall_name_fmt = 'outcall-{tenant_slug}-{outcall_uuid}' def __init__(self, tenant_dao, *args, **kwargs): super().__init__(*args, **kwargs) @@ -29,9 +30,11 @@ def post(self): form = self.add_tenant_to_form(form) tenant = self._tenant_dao.get(form['tenant_uuid']) + # NOTE(afournier): we use a UUID as if it was the outcall UUID but it's not + # Outcalls do not use UUIDs yet form['name'] = self.outcall_name_fmt.format( tenant_slug=tenant.slug, - outcall_id=form['id'], + outcall_uuid=uuid4(), ) model = self.model(**form) model = self.service.create(model) From e97c38e9cb431d69f886e027c44813336a28c725 Mon Sep 17 00:00:00 2001 From: Alexandre Fournier Date: Thu, 6 Jul 2023 15:19:16 -0400 Subject: [PATCH 3/4] changelog: outcalls name are now autogenerated --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 547d95be6..af31d08e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 23.10 + +* An outcall now has a read-only auto-generated `name` and a required `label` instead + of a user-provided `name` and optional `label`. For compatibility purposes, if an outcall is + created using only a `name`, it will be used as the `label` and auto-generated. + ## 23.04 * PUT on `/users?recursive=true` updated, to provide a way to update lines, switchboards, agent, voicemail and incalls for a specific user. From 045c8997b1af8287f2d9858ca581bcb236999f15 Mon Sep 17 00:00:00 2001 From: Alexandre Fournier Date: Fri, 7 Jul 2023 09:28:29 -0400 Subject: [PATCH 4/4] outcalls: fix tests about label/names --- integration_tests/suite/base/test_outcalls.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/integration_tests/suite/base/test_outcalls.py b/integration_tests/suite/base/test_outcalls.py index 3e092f3fb..9551a4e24 100644 --- a/integration_tests/suite/base/test_outcalls.py +++ b/integration_tests/suite/base/test_outcalls.py @@ -47,13 +47,6 @@ def error_checks(url): ) yield s.check_bogus_field_returns_error, url, 'preprocess_subroutine', [] yield s.check_bogus_field_returns_error, url, 'preprocess_subroutine', {} - yield s.check_bogus_field_returns_error, url, 'name', True - yield s.check_bogus_field_returns_error, url, 'name', None - yield s.check_bogus_field_returns_error, url, 'name', s.random_string(129) - yield s.check_bogus_field_returns_error, url, 'name', 1234 - yield s.check_bogus_field_returns_error, url, 'name', [] - yield s.check_bogus_field_returns_error, url, 'name', {} - yield s.check_bogus_field_returns_error, url, 'name', 'name' yield s.check_bogus_field_returns_error, url, 'label', 123 yield s.check_bogus_field_returns_error, url, 'label', None yield s.check_bogus_field_returns_error, url, 'label', True @@ -131,6 +124,7 @@ def test_get(outcall): description=outcall['description'], internal_caller_id=outcall['internal_caller_id'], name=outcall['name'], + label=outcall['label'], ring_time=outcall['ring_time'], trunks=empty(), ), @@ -158,7 +152,7 @@ def test_create_minimal_parameters(): def test_create_all_parameters(): parameters = { - 'name': 'MyOutcall', + 'label': 'My Outcall', 'internal_caller_id': True, 'preprocess_subroutine': 'subroutine', 'ring_time': 10, @@ -183,7 +177,7 @@ def test_edit_minimal_parameters(outcall): @fixtures.outcall() def test_edit_all_parameters(outcall): parameters = { - 'name': 'MyOutcall', + 'label': 'MyOutcall', 'internal_caller_id': True, 'preprocess_subroutine': 'subroutine', 'ring_time': 10,