diff --git a/CHANGELOG.md b/CHANGELOG.md index be89b51975..cce96c3186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Allow configuration of outgoing webhook timeout via `OUTGOING_WEBHOOK_TIMEOUT` environment variable @kevindw-fluxys ([#3801](https://github.com/grafana/oncall/pull/3801)) +- Include teams info in users API ([#3817](https://github.com/grafana/oncall/pull/3817)) ### Fixed diff --git a/docs/sources/oncall-api-reference/users.md b/docs/sources/oncall-api-reference/users.md index 04adcf1d25..434a2afdf9 100644 --- a/docs/sources/oncall-api-reference/users.md +++ b/docs/sources/oncall-api-reference/users.md @@ -29,7 +29,8 @@ The above command returns JSON structured in the following way: ], "username": "alex", "role": "admin", - "timezone": "UTC" + "timezone": "UTC", + "teams": [] } ``` @@ -47,6 +48,7 @@ Use `{{API_URL}}/api/v1/users/current` to retrieve the current user. | `username` | Yes/org | User username | | `role` | No | One of: `user`, `observer`, `admin`. | | `timezone` | No | timezone of the user one of [time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | +| `teams` | No | List of team IDs the user belongs to | # List Users @@ -76,7 +78,8 @@ The above command returns JSON structured in the following way: ], "username": "alex", "role": "admin", - "timezone": "UTC" + "timezone": "UTC", + "teams": ["TAAM1K1NNEHAG"] } ], "current_page_number": 1, diff --git a/engine/apps/public_api/serializers/users.py b/engine/apps/public_api/serializers/users.py index 4c3df2f46b..de0a2fecea 100644 --- a/engine/apps/public_api/serializers/users.py +++ b/engine/apps/public_api/serializers/users.py @@ -7,8 +7,8 @@ class SlackUserIdentitySerializer(serializers.ModelSerializer): - user_id = serializers.CharField(source="slack_id") - team_id = serializers.CharField(source="slack_team_identity.slack_id") + user_id: str = serializers.CharField(source="slack_id") + team_id: str = serializers.CharField(source="slack_team_identity.slack_id") class Meta: model = SlackUserIdentity @@ -19,49 +19,51 @@ class Meta: class FastUserSerializer(serializers.ModelSerializer): - id = serializers.ReadOnlyField(read_only=True, source="public_primary_key") - email = serializers.EmailField(read_only=True) - role = serializers.SerializerMethodField() # LEGACY, should be removed eventually - is_phone_number_verified = serializers.SerializerMethodField() + id: str = serializers.ReadOnlyField(read_only=True, source="public_primary_key") + email: str = serializers.EmailField(read_only=True) + role: str = serializers.SerializerMethodField() # LEGACY, should be removed eventually + is_phone_number_verified: bool = serializers.SerializerMethodField() class Meta: model = User fields = ["id", "email", "username", "role", "is_phone_number_verified"] @staticmethod - def get_role(obj): + def get_role(obj: User) -> str: """ LEGACY, should be removed eventually """ return LegacyAccessControlRole(obj.role).name.lower() - def get_is_phone_number_verified(self, obj): + def get_is_phone_number_verified(self, obj: User) -> bool: return obj.verified_phone_number is not None class UserSerializer(serializers.ModelSerializer, EagerLoadingMixin): - id = serializers.ReadOnlyField(read_only=True, source="public_primary_key") - email = serializers.EmailField(read_only=True) - slack = SlackUserIdentitySerializer(read_only=True, source="slack_user_identity") - role = serializers.SerializerMethodField() # LEGACY, should be removed eventually - is_phone_number_verified = serializers.SerializerMethodField() + id: str = serializers.ReadOnlyField(read_only=True, source="public_primary_key") + email: str = serializers.EmailField(read_only=True) + slack: SlackUserIdentity = SlackUserIdentitySerializer(read_only=True, source="slack_user_identity") + role: str = serializers.SerializerMethodField() # LEGACY, should be removed eventually + is_phone_number_verified: bool = serializers.SerializerMethodField() + teams: list[str] = serializers.SlugRelatedField(read_only=True, many=True, slug_field="public_primary_key") SELECT_RELATED = [ "slack_user_identity", "slack_user_identity__slack_team_identity", ] + PREFETCH_RELATED = ["teams"] class Meta: model = User - fields = ["id", "email", "slack", "username", "role", "is_phone_number_verified", "timezone"] + fields = ["id", "email", "slack", "username", "role", "is_phone_number_verified", "timezone", "teams"] read_only_fields = ["timezone"] @staticmethod - def get_role(obj): + def get_role(obj: User) -> str: """ LEGACY, should be removed eventually """ return LegacyAccessControlRole(obj.role).name.lower() - def get_is_phone_number_verified(self, obj): + def get_is_phone_number_verified(self, obj: User) -> bool: return obj.verified_phone_number is not None diff --git a/engine/apps/public_api/tests/test_users.py b/engine/apps/public_api/tests/test_users.py index 433ac4854b..3c7cd54267 100644 --- a/engine/apps/public_api/tests/test_users.py +++ b/engine/apps/public_api/tests/test_users.py @@ -35,6 +35,7 @@ def test_get_user( "role": "admin", "is_phone_number_verified": False, "timezone": user.timezone, + "teams": [], } assert response.status_code == status.HTTP_200_OK @@ -51,10 +52,13 @@ def test_get_user( @pytest.mark.django_db def test_get_users_list( user_public_api_setup, + make_team, make_user_for_organization, ): organization, user_1, token, slack_team_identity, slack_user_identity = user_public_api_setup + team = make_team(organization) user_2 = make_user_for_organization(organization) + user_2.teams.add(team) client = APIClient() @@ -74,6 +78,7 @@ def test_get_users_list( "role": "admin", "is_phone_number_verified": False, "timezone": user_1.timezone, + "teams": [], }, { "id": user_2.public_primary_key, @@ -83,6 +88,7 @@ def test_get_users_list( "role": "admin", "is_phone_number_verified": False, "timezone": user_2.timezone, + "teams": [team.public_primary_key], }, ], "current_page_number": 1,