diff --git a/config/locales/en.yml b/config/locales/en.yml index bfca26aeffaf..a397818825ec 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2025,6 +2025,7 @@ en: label_member_new: "New member" label_member_all_admin: "(All roles due to admin status)" label_member_plural: "Members" + label_membership_plural: "Memberships" lable_membership_added: "Member added" lable_membership_updated: "Member updated" label_menu_badge: diff --git a/docs/api/apiv3/components/examples/group-response.yml b/docs/api/apiv3/components/examples/group-response.yml new file mode 100644 index 000000000000..46277c8082f6 --- /dev/null +++ b/docs/api/apiv3/components/examples/group-response.yml @@ -0,0 +1,42 @@ +# Example: GroupResponse +--- +value: + _type: Group + id: 26 + name: Force Users + createdAt: '2024-01-11T15:54:16.542Z' + updatedAt: '2024-01-11T15:58:02.237Z' + _embedded: + members: + - _hint: Principal resource shortened for brevity + _type: User + id: 23 + name: Grogu Jarin + - _hint: Principal resource shortened for brevity + _type: User + id: 14 + name: Mara Jade + - _hint: Principal resource shortened for brevity + _type: User + id: 3 + name: Darth Vader + _links: + self: + href: '/api/v3/groups/26' + title: Force Users + memberships: + href: '/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%2226%22%5D%7D%7D%5D' + title: Memberships + delete: + href: '/api/v3/groups/26' + method: delete + updateImmediately: + href: '/api/v3/groups/26' + method: patch + members: + - href: '/api/v3/users/23' + title: Grogu Jarin + - href: '/api/v3/users/14' + title: Mara Jade + - href: '/api/v3/users/3' + title: Darth Vader diff --git a/docs/api/apiv3/components/examples/placeholder-user-response.yml b/docs/api/apiv3/components/examples/placeholder-user-response.yml new file mode 100644 index 000000000000..33052d030f2e --- /dev/null +++ b/docs/api/apiv3/components/examples/placeholder-user-response.yml @@ -0,0 +1,26 @@ +# Example: PlaceholderUserResponse +--- +value: + _type: PlaceholderUser + id: 27 + name: Akolyth + createdAt: '2024-02-12T11:52:24.708Z' + updatedAt: '2024-02-12T11:52:24.708Z' + _links: + self: + href: '/api/v3/placeholder_users/27' + title: Akolyth + memberships: + href: '/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%2227%22%5D%7D%7D%5D' + title: Memberships + updateImmediately: + href: '/api/v3/placeholder_users/27' + title: Update Akolyth + method: patch + delete: + href: '/api/v3/placeholder_users/27' + title: Delete Akolyth + method: delete + showUser: + href: '/placeholder_users/27' + type: text/html diff --git a/docs/api/apiv3/components/examples/user-response.yml b/docs/api/apiv3/components/examples/user-response.yml new file mode 100644 index 000000000000..729d29afba41 --- /dev/null +++ b/docs/api/apiv3/components/examples/user-response.yml @@ -0,0 +1,39 @@ +# Example: UserResponse +--- +value: + _type: User + id: 14 + name: Mara Jade + createdAt: '2022-04-04T08:07:22.910Z' + updatedAt: '2024-02-09T09:01:17.382Z' + login: member + admin: false + firstName: Mara + lastName: Jade + email: m.jade@empire.org + avatar: 'https://secure.gravatar.com/avatar/17dd23570f3bd129d06db9b48b7a41b8?default=404&secure=true' + status: active + identityUrl: null + language: en + _links: + self: + href: '/api/v3/users/14' + title: Mara Jade + memberships: + href: '/api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%2214%22%5D%7D%7D%5D' + title: Memberships + showUser: + href: '/users/14' + type: text/html + updateImmediately: + href: '/api/v3/users/14' + title: Update member + method: patch + lock: + href: '/api/v3/users/14/lock' + title: Set lock on member + method: post + delete: + href: '/api/v3/users/14' + title: Delete member + method: delete diff --git a/docs/api/apiv3/components/schemas/group_collection_model.yml b/docs/api/apiv3/components/schemas/group_collection_model.yml index 358bed756054..7a9b17d4a139 100644 --- a/docs/api/apiv3/components/schemas/group_collection_model.yml +++ b/docs/api/apiv3/components/schemas/group_collection_model.yml @@ -14,11 +14,11 @@ allOf: properties: self: allOf: - - $ref: "./link.yml" + - $ref: './link.yml' - description: |- This group collection - **Resource**: GroupCollectionReadModel + **Resource**: Collection _embedded: type: object required: diff --git a/docs/api/apiv3/components/schemas/group_model.yml b/docs/api/apiv3/components/schemas/group_model.yml index bb2753286c99..e5818c4f088b 100644 --- a/docs/api/apiv3/components/schemas/group_model.yml +++ b/docs/api/apiv3/components/schemas/group_model.yml @@ -1,107 +1,57 @@ # Schema: GroupModel --- -type: object -required: - - _type - - id - - _links -properties: - id: - type: integer - description: The group id - minimum: 1 - name: - type: string - description: |- - Group's full name, formatting depends on instance settings - - # Conditions - - admin - createdAt: - type: string - format: date-time - description: |- - Time of creation - - # Conditions - - admin - updatedAt: - type: string - format: date-time - description: Time of the most recent change to the user - _links: - type: object +allOf: + - $ref: './principal_model.yml' + - type: object required: - - self + - _type + - _embedded properties: - self: - allOf: - - $ref: './link.yml' - - description: |- - This group resource - - **Resource**: Group - delete: - allOf: - - $ref: './link.yml' - - description: |- - Deletes the group. - - # Conditions - **Permission**: Administrator - updateImmediately: - allOf: - - $ref: './link.yml' - - description: |- - Updates the group's attributes. - - # Conditions - **Permission**: Administrator - memberships: - allOf: - - $ref: './link.yml' - - description: |- - Link to collection of all the group's memberships. The list will only include the memberships in projects - in which the requesting user has the necessary permissions. - - **Resource**: MemberCollection - - # Conditions - **Permission**: `view members` or `manage members` in any project - members: - type: array - items: - allOf: - - $ref: './link.yml' - - description: |- - One of the members of the group. - - **Resource**: User - - # Conditions - **Permission**: `manage members` in any project to read & admin to write - -example: - _type: Group - id: 9 - name: Stormtroopers - createdAt: '2022-09-23T11:06:36.024Z' - updatedAt: '2022-09-23T11:06:36.024Z' - _links: - self: - href: '/api/v3/groups/9' - title: Stormtroopers - delete: - href: '/api/v3/group/9' - method: delete - memberships: - href: '/api/v3/memberships?filters=[{"principal":{"operator":"=","values":["9"]}}]' - title: Memberships - updateImmediately: - href: '/api/v3/group/9' - method: patch - members: - - href: '/api/v3/users/363' - title: ST-097E - - href: '/api/v3/users/60' - title: ST-C-334 + _type: + type: string + enum: + - Group + _embedded: + type: object + properties: + members: + type: array + description: Embedded list of members. + items: + - $ref: './user_model.yml' + _links: + type: object + properties: + members: + type: array + items: + allOf: + - $ref: './link.yml' + - description: |- + A member of the group + + # Conditions: + + - user has permission `manage_members` in any project + + **Resource**: User + delete: + allOf: + - $ref: './link.yml' + - description: |- + An href to delete the group. + + # Conditions: + + - `admin` + updateImmediately: + allOf: + - $ref: './link.yml' + - description: |- + An href to update the group. + + # Conditions: + + - `admin` + + **Resource**: Group diff --git a/docs/api/apiv3/components/schemas/placeholder_user_collection_model.yml b/docs/api/apiv3/components/schemas/placeholder_user_collection_model.yml new file mode 100644 index 000000000000..36a2bf177682 --- /dev/null +++ b/docs/api/apiv3/components/schemas/placeholder_user_collection_model.yml @@ -0,0 +1,46 @@ +# Schema: PlaceholderUserCollectionModel +--- +allOf: + - $ref: './collection_model.yml' + - type: object + required: + - _links + - _embedded + properties: + _links: + type: object + required: + - self + properties: + self: + allOf: + - $ref: "./link.yml" + - description: |- + This placeholder user collection + + **Resource**: Collection + _embedded: + type: object + required: + - elements + properties: + elements: + type: array + items: + $ref: './placeholder_user_model.yml' + +example: + _type: Collection + total: 2 + count: 2 + _links: + self: + href: '/api/v3/placeholder_users' + _embedded: + elements: + - _hint: PlaceholderUser resource shortened for brevity + _type: PlaceholderUser + id: 1337 + - _hint: PlaceholderUser resource shortened for brevity + _type: PlaceholderUser + id: 1338 diff --git a/docs/api/apiv3/components/schemas/placeholder_user_create_model.yml b/docs/api/apiv3/components/schemas/placeholder_user_create_model.yml new file mode 100644 index 000000000000..be2fd48855ac --- /dev/null +++ b/docs/api/apiv3/components/schemas/placeholder_user_create_model.yml @@ -0,0 +1,7 @@ +# Schema: PlaceholderUserCreateModel +--- +type: object +properties: + name: + type: string + description: The new name of the placeholder user to be created. diff --git a/docs/api/apiv3/components/schemas/placeholder_user_model.yml b/docs/api/apiv3/components/schemas/placeholder_user_model.yml new file mode 100644 index 000000000000..7193afd18cc2 --- /dev/null +++ b/docs/api/apiv3/components/schemas/placeholder_user_model.yml @@ -0,0 +1,50 @@ +# Schema: PlaceholderUserModel +--- +allOf: + - $ref: './principal_model.yml' + - type: object + required: + - _type + properties: + _type: + type: string + enum: + - PlaceholderUser + status: + type: string + description: |- + The current activation status of the placeholder user. + + # Conditions + + - User has `manage_placeholder_user` permission globally + _links: + type: object + required: + - showUser + properties: + showUser: + allOf: + - $ref: './link.yml' + - description: |- + A relative path to show the placeholder user in the web application. + delete: + allOf: + - $ref: './link.yml' + - description: |- + An href to delete the placeholder user. + + # Conditions: + + - `manage_placeholder_user` + updateImmediately: + allOf: + - $ref: './link.yml' + - description: |- + An href to update the placeholder user. + + # Conditions: + + - `manage_placeholder_user` + + **Resource**: PlaceholderUser diff --git a/docs/api/apiv3/components/schemas/principal_collection_model.yml b/docs/api/apiv3/components/schemas/principal_collection_model.yml new file mode 100644 index 000000000000..7ca06dde3128 --- /dev/null +++ b/docs/api/apiv3/components/schemas/principal_collection_model.yml @@ -0,0 +1,53 @@ +# Schema: PrincipalCollectionModel +--- +allOf: + - $ref: './collection_model.yml' + - type: object + required: + - _links + - _embedded + properties: + _links: + type: object + required: + - self + - representations + properties: + self: + allOf: + - $ref: "./link.yml" + - description: |- + This principal collection + + **Resource**: Collection + _embedded: + type: object + required: + - elements + properties: + elements: + type: array + items: + anyOf: + - $ref: './user_model.yml' + - $ref: './placeholder_user_model.yml' + - $ref: './group_model.yml' + +example: + _type: Collection + total: 3 + count: 3 + _links: + self: + href: '/api/v3/principals' + _embedded: + elements: + - _hint: User resource shortened for brevity + _type: User + id: 1337 + - _hint: Group resource shortened for brevity + _type: Group + id: 1338 + - _hint: PlaceholderUser resource shortened for brevity + _type: PlaceholderUser + id: 1339 diff --git a/docs/api/apiv3/components/schemas/principal_model.yml b/docs/api/apiv3/components/schemas/principal_model.yml new file mode 100644 index 000000000000..10f31f6e821f --- /dev/null +++ b/docs/api/apiv3/components/schemas/principal_model.yml @@ -0,0 +1,53 @@ +# Schema: PrincipalModel +--- +type: object +required: + - _type + - id + - name + - _links +properties: + _type: + type: string + enum: + - User + - Group + - PlaceholderUser + id: + type: integer + description: The principal's unique identifier. + minimum: 1 + name: + type: string + description: The principal's display name, layout depends on instance settings. + createdAt: + type: string + format: date-time + description: Time of creation + updatedAt: + type: string + format: date-time + description: Time of the most recent change to the principal + _links: + type: object + required: + - self + properties: + self: + allOf: + - $ref: './link.yml' + - description: |- + This principal resource. + + **Resource**: User|Group|PlaceholderUser + memberships: + allOf: + - $ref: './link.yml' + - description: |- + An href to the collection of the principal's memberships. + + # Conditions: + + - user has permission `view_members` or `manage_members` in any project + + **Resource**: Collection diff --git a/docs/api/apiv3/components/schemas/principals_model.yml b/docs/api/apiv3/components/schemas/principals_model.yml deleted file mode 100644 index f18694db82fe..000000000000 --- a/docs/api/apiv3/components/schemas/principals_model.yml +++ /dev/null @@ -1,94 +0,0 @@ -# Schema: PrincipalsModel ---- -type: object -example: - _type: Collection - total: 4 - count: 4 - _embedded: - elements: - - _type: User - id: 4 - login: Eliza92778 - admin: false - firstName: Danika - lastName: O'Keefe - name: Danika O'Keefe - email: jackie@dicki.org - avatar: https://example.org/users/4/avatar - createdAt: '2015-03-20T12:57:02.646Z' - updatedAt: '2015-06-16T15:28:14.550Z' - status: active - identityUrl: - _links: - self: - href: "/api/v3/users/4" - title: Danika O'Keefe - showUser: - href: "/users/4" - type: text/html - updateImmediately: - href: "/api/v3/users/4" - title: Update Eliza92778 - method: patch - lock: - href: "/api/v3/users/4/lock" - title: Set lock on Eliza92778 - method: post - delete: - href: "/api/v3/users/4" - title: Delete Eliza92778 - method: delete - - _type: User - id: 2 - login: Sebastian9686 - admin: false - firstName: Peggie - lastName: Feeney - name: Peggie Feeney - email: - avatar: https://example.org/users/4/avatar - createdAt: '2015-03-20T12:56:55.963Z' - updatedAt: '2015-03-20T12:56:55.963Z' - status: active - identityUrl: - _links: - self: - href: "/api/v3/users/2" - title: Peggie Feeney - showUser: - href: "/users/2" - type: text/html - updateImmediately: - href: "/api/v3/users/2" - title: Update Sebastian9686 - method: patch - lock: - href: "/api/v3/users/2/lock" - title: Set lock on Sebastian9686 - method: post - delete: - href: "/api/v3/users/2" - title: Delete Sebastian9686 - method: delete - - _type: Group - id: 9 - name: The group - createdAt: '2015-09-23T11:06:36.687Z' - updatedAt: '2015-09-23T11:06:36.687Z' - _links: - self: - href: "/api/v3/groups/9" - title: The group - - _type: PlaceholderUser - id: 29 - name: UX Designer - createdAt: '2018-09-23T11:06:36.687Z' - updatedAt: '2019-10-23T11:06:36.687Z' - _links: - self: - href: "/api/v3/placeholder_users/29" - title: UX Designer - _links: - self: - href: "/api/v3/principals" diff --git a/docs/api/apiv3/components/schemas/project_collection_model.yml b/docs/api/apiv3/components/schemas/project_collection_model.yml index 298bfa547189..ec34cb9e5b4f 100644 --- a/docs/api/apiv3/components/schemas/project_collection_model.yml +++ b/docs/api/apiv3/components/schemas/project_collection_model.yml @@ -15,16 +15,16 @@ allOf: properties: self: allOf: - - $ref: "./link.yml" + - $ref: './link.yml' - description: |- - This file links collection + This project collection **Resource**: ProjectStorageCollectionModel representations: type: array items: allOf: - - $ref: "./link.yml" + - $ref: './link.yml' - description: |- A project collection representation in a specific file format. _embedded: diff --git a/docs/api/apiv3/components/schemas/user_collection_model.yml b/docs/api/apiv3/components/schemas/user_collection_model.yml index e6aa2ab589d4..f803db5f7ac9 100644 --- a/docs/api/apiv3/components/schemas/user_collection_model.yml +++ b/docs/api/apiv3/components/schemas/user_collection_model.yml @@ -14,11 +14,11 @@ allOf: properties: self: allOf: - - $ref: "./link.yml" + - $ref: './link.yml' - description: |- This user collection - **Resource**: UserCollectionModel + **Resource**: Collection _embedded: type: object required: diff --git a/docs/api/apiv3/components/schemas/user_model.yml b/docs/api/apiv3/components/schemas/user_model.yml index f93995ce4ac7..97a850cb8a4e 100644 --- a/docs/api/apiv3/components/schemas/user_model.yml +++ b/docs/api/apiv3/components/schemas/user_model.yml @@ -1,201 +1,163 @@ # Schema: UserModel --- -type: object -required: - - _type - - id - - name - - avatar - - _links -properties: - _type: - type: string - enum: - - User - id: - type: integer - description: User's id - minimum: 0 - login: - type: string - description: |- - User's login name - - # Conditions - - **Permission**: Administrator, manage_user global permission - maxLength: 256 - firstName: - type: string - description: |- - User's first name - - # Conditions - - **Permission**: Administrator, manage_user global permission - maxLength: 30 - lastName: - type: string - description: |- - User's last name - - # Conditions - - **Permission**: Administrator, manage_user global permission - maxLength: 30 - name: - type: string - description: User's full name, formatting depends on instance settings - email: - type: string - description: |- - User's email address - - # Conditions - - E-Mail address not hidden, **Permission**: Administrator, manage_user global permission - maxLength: 60 - admin: - type: boolean - description: |- - Flag indicating whether or not the user is an admin - - # Conditions - - **Permission**: Administrator - avatar: - type: string - format: uri - description: URL to user's avatar - status: - type: string - description: The current activation status of the user (see below) - language: - type: string - description: |- - User's language | ISO 639-1 format - - # Conditions - - **Permission**: Administrator, manage_user global permission - identityUrl: - type: - - 'string' - - 'null' - description: |- - User's identity_url for OmniAuth authentication - - # Conditions - - **Permission**: Administrator - createdAt: - type: string - format: date-time - description: Time of creation - updatedAt: - type: string - format: date-time - description: Time of the most recent change to the user - _links: - type: object +allOf: + - $ref: './principal_model.yml' + - type: object required: - - self - - memberships - - showUser + - _type + - avatar properties: - self: - allOf: - - "$ref": "./link.yml" - - description: |- - This user - - **Resource**: User - memberships: - allOf: - - "$ref": "./link.yml" - - description: |- - Link to collection of all the user's memberships. The list will only include the memberships in projects in which the requesting user has the necessary permissions. - - **Resource**: MemberCollection - - # Conditions - - **Permission**: view members or manage members in any project - showUser: - allOf: - - "$ref": "./link.yml" - - description: Link to the OpenProject user page (HTML) - updateImmediately: - allOf: - - "$ref": "./link.yml" - - description: |- - Updates the user's attributes. - - # Conditions - - **Permission**: Administrator, manage_user global permission - lock: - allOf: - - "$ref": "./link.yml" - - description: |- - Restrict the user from logging in and performing any actions - - # Conditions - - not locked; **Permission**: Administrator - unlock: - allOf: - - "$ref": "./link.yml" - - description: |- - Allow a locked user to login and act again - - # Conditions - - locked; **Permission**: Administrator - delete: - allOf: - - "$ref": "./link.yml" - - description: |- - Permanently remove a user from the instance - - # Conditions - - **Permission**: Administrator, self-delete - -example: - _type: User - id: 1 - name: John Sheppard - login: j.sheppard - firstName: John - lastName: Sheppard - email: shep@mail.com - admin: true - avatar: https://example.org/users/1/avatar - status: active - identityUrl: null, - language: en - createdAt: '2014-05-21T08:51:20.396Z' - updatedAt: '2014-05-21T08:51:20.396Z' - _links: - self: - href: '/api/v3/users/1' - title: John Sheppard - memberships: - href: /api/v3/memberships?filters=%5B%7B%22principal%22%3A%7B%22operator%22%3A%22%3D%22%2C%22values%22%3A%5B%221%22%5D%7D%7D%5D, - title: Members - showUser: - href: '/users/1' - type: text/html - lock: - href: '/api/v3/users/1/lock' - title: Set lock on John Sheppard - method: post - updateImmediately: - href: '/api/v3/users/1' - title: update John Sheppard - method: patch - delete: - href: '/api/v3/users/1' - title: delete John Sheppard - method: delete + _type: + type: string + enum: + - User + avatar: + type: string + format: uri + description: URL to user's avatar + login: + type: string + description: |- + The user's login name + + # Conditions + + - User is self, or `create_user` or `manage_user` permission globally + maxLength: 256 + firstName: + type: string + description: |- + The user's first name + + # Conditions + + - User is self, or `create_user` or `manage_user` permission globally + maxLength: 30 + lastName: + type: string + description: |- + The user's last name + + # Conditions + + - User is self, or `create_user` or `manage_user` permission globally + maxLength: 30 + email: + type: string + description: |- + The user's email address + + # Conditions + + - E-Mail address not hidden + - User is not a new record + - User is self, or `create_user` or `manage_user` permission globally + maxLength: 60 + admin: + type: boolean + description: |- + Flag indicating whether or not the user is an admin + + # Conditions + + - `admin` + status: + type: string + description: |- + The current activation status of the user. + + # Conditions + + - User is self, or `create_user` or `manage_user` permission globally + language: + type: string + description: |- + User's language | ISO 639-1 format + + # Conditions + + - User is self, or `create_user` or `manage_user` permission globally + identityUrl: + type: + - 'string' + - 'null' + description: |- + User's identity_url for OmniAuth authentication. + + # Conditions + + - User is self, or `create_user` or `manage_user` permission globally + createdAt: + type: string + format: date-time + description: Time of creation + updatedAt: + type: string + format: date-time + description: Time of the most recent change to the user + _links: + type: object + properties: + showUser: + allOf: + - $ref: './link.yml' + - description: |- + A relative path to show the user in the web application. + + # Condition + + - User is not a new record + - User is not `locked` + updateImmediately: + allOf: + - $ref: './link.yml' + - description: |- + A link to update the user resource. + + # Conditions + + - `admin` + lock: + allOf: + - $ref: './link.yml' + - description: |- + Restrict the user from logging in and performing any actions. + + # Conditions + + - User is not locked + - `admin` + unlock: + allOf: + - $ref: './link.yml' + - description: |- + Allow a locked user to login and act again. + + # Conditions + + - User is not locked + - `admin` + delete: + allOf: + - $ref: './link.yml' + - description: |- + Permanently remove a user from the instance + + # Conditions + + either: + - `admin` + - Setting `users_deletable_by_admin` is set + or: + - User is self + - Setting `users_deletable_by_self` is set + authSource: + allOf: + - $ref: './link.yml' + - description: |- + Permanently remove a user from the instance + + # Conditions + + - LDAP authentication configured + - `admin` diff --git a/docs/api/apiv3/openapi-spec.yml b/docs/api/apiv3/openapi-spec.yml index a5012089fafe..30ae39bbb872 100644 --- a/docs/api/apiv3/openapi-spec.yml +++ b/docs/api/apiv3/openapi-spec.yml @@ -256,6 +256,10 @@ paths: "$ref": "./paths/oauth_application.yml" "/api/v3/oauth_client_credentials/{id}": "$ref": "./paths/oauth_client_credentials.yml" + "/api/v3/placeholder_users": + "$ref": "./paths/placeholder_users.yml" + "/api/v3/placeholder_users/{id}": + "$ref": "./paths/placeholder_user.yml" "/api/v3/posts/{id}": "$ref": "./paths/post.yml" "/api/v3/posts/{id}/attachments": @@ -475,6 +479,8 @@ components: $ref: './components/examples/grid-simple-patch-model.yml' GridSimpleResponse: $ref: './components/examples/grid-simple-response.yml' + GroupResponse: + $ref: './components/examples/group-response.yml' MembershipCreateRequestCustomMessage: $ref: './components/examples/membership-create-request-custom-message.yml' MembershipCreateRequestGlobalRole: @@ -495,6 +501,8 @@ components: $ref: './components/examples/mentioned_notification.yml' NotificationCollection: $ref: './components/examples/notification_collection.yml' + PlaceholderUserResponse: + $ref: './components/examples/placeholder-user-response.yml' Project: $ref: './components/examples/project.yml' ProjectBody: @@ -511,6 +519,8 @@ components: $ref: './components/examples/status_collection.yml' StoragesSimpleCollectionModel: $ref: './components/examples/storages-simple-collection-response.yml' + UserResponse: + $ref: './components/examples/user-response.yml' ValuesPropertyStartDateSchema: $ref: './components/examples/values_property_start_date_schema.yml' ValuesPropertyDueDateSchema: @@ -669,12 +679,20 @@ components: "$ref": "./components/schemas/oauth_client_credentials_write_model.yml" PaginatedCollectionModel: "$ref": "./components/schemas/paginated_collection_model.yml" + PlaceholderUserCollectionModel: + "$ref": "./components/schemas/placeholder_user_collection_model.yml" + PlaceholderUserCreateModel: + "$ref": "./components/schemas/placeholder_user_create_model.yml" + PlaceholderUserModel: + "$ref": "./components/schemas/placeholder_user_model.yml" Plain_TextModel: "$ref": "./components/schemas/plain_text_model.yml" PostModel: "$ref": "./components/schemas/post_model.yml" - PrincipalsModel: - "$ref": "./components/schemas/principals_model.yml" + PrincipalCollectionModel: + "$ref": "./components/schemas/principal_collection_model.yml" + PrincipalModel: + "$ref": "./components/schemas/principal_model.yml" PrioritiesModel: "$ref": "./components/schemas/priorities_model.yml" PriorityModel: diff --git a/docs/api/apiv3/paths/group.yml b/docs/api/apiv3/paths/group.yml index dae8e3c6988d..f45dd4465fb3 100644 --- a/docs/api/apiv3/paths/group.yml +++ b/docs/api/apiv3/paths/group.yml @@ -17,7 +17,7 @@ delete: responses: '202': description: |- - Returned if the group was successfully deleted + Returned if the group was marked for deletion. Note that the response body is empty as of now. In future versions of the API a body *might* be returned, indicating the progress of deletion. @@ -79,6 +79,9 @@ get: application/hal+json: schema: $ref: '../components/schemas/group_model.yml' + examples: + 'group response': + $ref: '../components/examples/group-response.yml' '404': description: |- Returned if the group does not exist or if the API user does not have permission to view them. diff --git a/docs/api/apiv3/paths/placeholder_user.yml b/docs/api/apiv3/paths/placeholder_user.yml new file mode 100644 index 000000000000..f4609c601da3 --- /dev/null +++ b/docs/api/apiv3/paths/placeholder_user.yml @@ -0,0 +1,161 @@ +# /api/v3/placeholder_users/{id} +--- +delete: + summary: Delete placeholder user + operationId: delete_placeholder_user + description: Set the specified placeholder user to deleted status. + tags: + - Principals + parameters: + - description: Placeholder user id + example: '1' + in: path + name: id + required: true + schema: + type: integer + responses: + '202': + description: |- + Returned if the group was marked for deletion. + + Note that the response body is empty as of now. In future versions of the API a body + *might* be returned, indicating the progress of deletion. + '403': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:MissingPermission + message: You are not allowed to delete the account of this user. + description: |- + Returned if the client does not have sufficient permissions. + + **Required permission:** `manage_placeholder_users` + '404': + description: Returned if the placeholder user does not exist. + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:NotFound + message: The specified placeholder user does not exist. + +get: + summary: View placeholder user + operationId: view_placeholder_user + description: Return the placeholder user resource. + tags: + - Principals + parameters: + - description: The placeholder user id + example: '1' + in: path + name: id + required: true + schema: + type: string + responses: + '200': + content: + application/hal+json: + schema: + $ref: '../components/schemas/placeholder_user_model.yml' + examples: + 'placeholder user response': + $ref: '../components/examples/placeholder-user-response.yml' + description: OK + '404': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:NotFound + message: The specified placeholder user does not exist or you do not have permission to view them. + description: |- + Returned if the user does not exist or if the API user does not have permission to view them. + + **Required permission**: `manage_placeholder_users` + +patch: + summary: Update placeholder user + operationId: update_placeholder_user + tags: + - Principals + description: |- + Updates the placeholder user's writable attributes. + When calling this endpoint the client provides a single object, containing at least the properties and links + that are required, in the body. + parameters: + - description: Placeholder user id + example: '1' + in: path + name: id + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '../components/schemas/placeholder_user_create_model.yml' + responses: + '200': + content: + application/hal+json: + schema: + $ref: '../components/schemas/placeholder_user_model.yml' + description: OK + '400': + $ref: "../components/responses/invalid_request_body.yml" + '403': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:MissingPermission + message: You are not allowed to update the account of this user. + description: |- + Returned if the client does not have sufficient permissions. + + **Required permission**: `manage_placeholder_users` + '404': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:NotFound + message: The specified placeholder user does not exist. + description: |- + Returned if the placeholder user does not exist. + '406': + $ref: "../components/responses/missing_content_type.yml" + '415': + $ref: "../components/responses/unsupported_media_type.yml" + '422': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _embedded: + details: + attribute: name + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:PropertyConstraintViolation + message: Name has already been taken. + description: |- + Returned if: + + - the client tries to modify a read-only property (`PropertyIsReadOnly`) + - a constraint for a property was violated (`PropertyConstraintViolation`) diff --git a/docs/api/apiv3/paths/placeholder_users.yml b/docs/api/apiv3/paths/placeholder_users.yml new file mode 100644 index 000000000000..39bb3acccd10 --- /dev/null +++ b/docs/api/apiv3/paths/placeholder_users.yml @@ -0,0 +1,108 @@ +# /api/v3/placeholder_users +--- +get: + summary: List placehoder users + operationId: list_placeholder_users + tags: + - Principals + description: |- + List all placeholder users. This can only be accessed if the requesting user has the global permission + `manage_placeholder_user` or `manage_members` in any project. + parameters: + - name: filters + description: |- + JSON specifying filter conditions. + Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint. + Currently supported filters are: + + - name: filters placeholder users by the name. + - group: filters placeholder by the group it is contained in. + - status: filters placeholder by the status it has. + in: query + required: false + schema: + type: string + example: '[{ "name": { "operator": "~", "values": ["Darth"] } }]' + - name: select + description: Comma separated list of properties to include. + in: query + required: false + schema: + type: string + example: 'total,elements/name,elements/self,self' + responses: + '200': + description: OK + content: + application/hal+json: + schema: + $ref: '../components/schemas/principal_collection_model.yml' + '400': + description: Returned if the client sends invalid request parameters e.g. filters + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:InvalidQuery + message: Filters Invalid filter does not exist. + +post: + summary: Create placeholder user + operationId: create_placeholder_user + tags: + - Principals + description: |- + Creates a new placeholder user. Only administrators and users with `manage_placeholder_user` global permission are + allowed to do so. When calling this endpoint the client provides a single object, containing at least the + properties and links that are required, in the body. + requestBody: + content: + application/json: + schema: + $ref: '../components/schemas/placeholder_user_create_model.yml' + responses: + '201': + content: + application/hal+json: + schema: + $ref: '../components/schemas/placeholder_user_model.yml' + description: Created + '400': + $ref: '../components/responses/invalid_request_body.yml' + '403': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:MissingPermission + message: You are not allowed to create new placeholder users. + description: |- + Returned if the client does not have sufficient permissions. + + **Required permission:** Administrator + '406': + $ref: '../components/responses/missing_content_type.yml' + '415': + $ref: '../components/responses/unsupported_media_type.yml' + '422': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _embedded: + details: + attribute: name + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:PropertyConstraintViolation + message: Name has already been taken. + description: |- + Returned if: + + * a constraint for a property was violated (`PropertyConstraintViolation`) + + diff --git a/docs/api/apiv3/paths/principals.yml b/docs/api/apiv3/paths/principals.yml index 1cdd47be7702..bc4c25c856aa 100644 --- a/docs/api/apiv3/paths/principals.yml +++ b/docs/api/apiv3/paths/principals.yml @@ -1,141 +1,52 @@ # /api/v3/principals --- get: + summary: List principals + operationId: list_principals + tags: + - Principals + description: |- + List all principals. The client can choose to filter the principals similar to how work packages are filtered. In + addition to the provided filters, the server will reduce the result set to only contain principals who are members + in projects the client is allowed to see. parameters: - - description: |- - JSON specifying filter conditions. - Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint. - Currently supported filters are: - - + type: filters principals by their type (*User*, *Group*, *PlaceholderUser*). - - + member: filters principals by the projects they are members in. - - + name: filters principals by the user or group name. - - + any_name_attribute: filters principals by the user or group first- and last name, email or login. - - + status: filters principals by their status number (active = *1*, registered = *2*, locked = *3*, invited = *4*) - example: '[{ "type": { "operator": "=", "values": ["User"] } }]' - in: query - name: filters - required: false - schema: - type: string - - description: |- - Comma separated list of properties to include. - example: 'total,elements/name,elements/self,self' - in: query - name: select - required: false - schema: - type: string + - name: filters + description: |- + JSON specifying filter conditions. + Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint. + Currently supported filters are: + + - type: filters principals by their type (*User*, *Group*, *PlaceholderUser*). + - member: filters principals by the projects they are members in. + - name: filters principals by the user or group name. + - any_name_attribute: filters principals by the user or group first- and last name, email or login. + - status: filters principals by their status number (active = *1*, registered = *2*, locked = *3*, invited = *4*) + in: query + required: false + schema: + type: string + example: '[{ "type": { "operator": "=", "values": ["User"] } }]' + - name: select + description: Comma separated list of properties to include. + in: query + required: false + schema: + type: string + example: 'total,elements/name,elements/self,self' responses: '200': + description: OK content: application/hal+json: - examples: - response: - value: - _embedded: - elements: - - _links: - delete: - href: "/api/v3/users/4" - method: delete - title: Delete Eliza92778 - lock: - href: "/api/v3/users/4/lock" - method: post - title: Set lock on Eliza92778 - self: - href: "/api/v3/users/4" - title: Danika O'Keefe - showUser: - href: "/users/4" - type: text/html - updateImmediately: - href: "/api/v3/users/4" - method: patch - title: Update Eliza92778 - _type: User - admin: false - avatar: https://example.org/users/4/avatar - createdAt: '2015-03-20T12:57:02.901Z' - email: jackie@dicki.org - firstName: Danika - id: 4 - identityUrl: - lastName: O'Keefe - login: Eliza92778 - name: Danika O'Keefe - status: active - updatedAt: '2015-06-16T15:28:14.565Z' - - _links: - delete: - href: "/api/v3/users/2" - method: delete - title: Delete Sebastian9686 - lock: - href: "/api/v3/users/2/lock" - method: post - title: Set lock on Sebastian9686 - self: - href: "/api/v3/users/2" - title: Peggie Feeney - showUser: - href: "/users/2" - type: text/html - updateImmediately: - href: "/api/v3/users/2" - method: patch - title: Update Sebastian9686 - _type: User - admin: false - avatar: https://example.org/users/4/avatar - createdAt: '2015-03-20T12:56:55.578Z' - email: - firstName: Peggie - id: 2 - identityUrl: - lastName: Feeney - login: Sebastian9686 - name: Peggie Feeney - status: active - updatedAt: '2015-03-20T12:56:55.254Z' - - _links: - self: - href: "/api/v3/groups/9" - title: The group - _type: Group - createdAt: '2015-09-23T11:06:36.231Z' - id: 9 - name: The group - updatedAt: '2015-09-23T11:06:36.231Z' - - _links: - self: - href: "/api/v3/placeholder_users/29" - title: UX Designer - _type: PlaceholderUser - createdAt: '2018-09-23T11:06:36.231Z' - id: 29 - name: UX Designer - updatedAt: '2019-10-23T11:06:36.231Z' - _links: - self: - href: "/api/v3/principals" - _type: Collection - count: 4 - total: 4 schema: - "$ref": "../components/schemas/principals_model.yml" - description: OK - headers: {} - tags: - - Principals - description: List all principals. The client can choose to filter the principals - similar to how work packages are filtered. In addition to the provided filters, - the server will reduce the result set to only contain principals who are members - in projects the client is allowed to see. - operationId: List_principals - summary: List principals + $ref: '../components/schemas/principal_collection_model.yml' + '400': + description: Returned if the client sends invalid request parameters e.g. filters + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:InvalidQuery + message: Filters Invalid filter does not exist. diff --git a/docs/api/apiv3/paths/user.yml b/docs/api/apiv3/paths/user.yml index cd996fde062e..b7e22c2c2455 100644 --- a/docs/api/apiv3/paths/user.yml +++ b/docs/api/apiv3/paths/user.yml @@ -6,6 +6,7 @@ delete: description: Permanently deletes the specified user account. tags: - Users + - Principals parameters: - description: User id example: '1' @@ -51,6 +52,7 @@ get: description: '' tags: - Users + - Principals parameters: - description: User id. Use `me` to reference current user, if any. example: '1' @@ -65,6 +67,9 @@ get: application/hal+json: schema: $ref: '../components/schemas/user_model.yml' + examples: + 'user response': + $ref: '../components/examples/user-response.yml' description: OK '404': content: @@ -85,6 +90,7 @@ patch: operationId: update_user tags: - Users + - Principals description: |- Updates the user's writable attributes. When calling this endpoint the client provides a single object, containing at least the properties and links that are required, in the body. diff --git a/docs/api/apiv3/paths/users.yml b/docs/api/apiv3/paths/users.yml index b9036967c48b..d31515008e28 100644 --- a/docs/api/apiv3/paths/users.yml +++ b/docs/api/apiv3/paths/users.yml @@ -3,9 +3,15 @@ get: summary: List Users operationId: list_Users - description: "Lists users. Only administrators or users with any of the following can access this resource: 'manage_members', 'manage_user', 'share_work_packages'." + description: |- + Lists users. Only administrators or users with any of the following can access this resource: + + - `manage_members` + - `manage_user` + - `share_work_packages` tags: - Users + - Principals parameters: - description: Page number inside the requested collection. example: '25' @@ -87,6 +93,7 @@ post: operationId: create_user tags: - Users + - Principals description: |- Creates a new user. Only administrators and users with manage_user global permission are allowed to do so. When calling this endpoint the client provides a single object, containing at least the properties and links that are required, in the body. diff --git a/lib/api/v3/placeholder_users/placeholder_user_representer.rb b/lib/api/v3/placeholder_users/placeholder_user_representer.rb index 758b3f662ec9..f783a96eda84 100644 --- a/lib/api/v3/placeholder_users/placeholder_user_representer.rb +++ b/lib/api/v3/placeholder_users/placeholder_user_representer.rb @@ -55,10 +55,21 @@ class PlaceholderUserRepresenter < ::API::V3::Principals::PrincipalRepresenter } end + property :status, + getter: ->(*) { represented.status }, + setter: ->(fragment:, represented:, **) { represented.status = User.statuses[fragment.to_sym] }, + exec_context: :decorator, + render_nil: true, + cache_if: -> { current_user_can_manage? } + def _type 'PlaceholderUser' end + def current_user_can_see_date_properties? + current_user_can_manage? + end + def current_user_can_manage? current_user&.allowed_globally?(:manage_placeholder_user) end diff --git a/lib/api/v3/principals/principal_representer.rb b/lib/api/v3/principals/principal_representer.rb index 33813ee011b6..7a9c07b6a36d 100644 --- a/lib/api/v3/principals/principal_representer.rb +++ b/lib/api/v3/principals/principal_representer.rb @@ -52,7 +52,7 @@ class PrincipalRepresenter < ::API::Decorators::Single { href: api_v3_paths.path_for(:memberships, filters:), - title: I18n.t(:label_member_plural) + title: I18n.t(:label_membership_plural) } end @@ -63,10 +63,14 @@ class PrincipalRepresenter < ::API::Decorators::Single render_nil: true date_time_property :created_at, - cache_if: -> { current_user_is_admin_or_self } + cache_if: -> { current_user_can_see_date_properties? } date_time_property :updated_at, - cache_if: -> { current_user_is_admin_or_self } + cache_if: -> { current_user_can_see_date_properties? } + + def current_user_can_see_date_properties? + current_user_is_admin_or_self + end def current_user_is_admin_or_self current_user_is_admin? || current_user_is_self? diff --git a/spec/lib/api/v3/placeholder_users/placeholder_user_representer_rendering_spec.rb b/spec/lib/api/v3/placeholder_users/placeholder_user_representer_rendering_spec.rb index 122056d9eb1c..030fa8d2c30c 100644 --- a/spec/lib/api/v3/placeholder_users/placeholder_user_representer_rendering_spec.rb +++ b/spec/lib/api/v3/placeholder_users/placeholder_user_representer_rendering_spec.rb @@ -59,7 +59,7 @@ end describe '_links' do - context 'self' do + describe 'self' do it_behaves_like 'has a titled link' do let(:link) { 'self' } let(:href) { api_v3_paths.placeholder_user placeholder_user.id } @@ -67,19 +67,19 @@ end end - context 'showUser' do + describe 'showUser' do it_behaves_like 'has an untitled link' do let(:link) { 'showUser' } let(:href) { "/placeholder_users/#{placeholder_user.id}" } end end - context 'delete' do + describe 'delete' do it_behaves_like 'has no link' do let(:link) { 'delete' } end - context 'when user allowed to manage' do + context 'if user is allowed to manage' do let(:global_permissions) { [:manage_placeholder_user] } it_behaves_like 'has a titled link' do @@ -91,7 +91,7 @@ end end - context 'updateImmediately' do + describe 'updateImmediately' do it_behaves_like 'has no link' do let(:link) { 'updateImmediately' } end @@ -108,28 +108,28 @@ end end - context 'memberships' do + describe 'memberships' do it_behaves_like 'has no link' do let(:link) { 'memberships' } end - context 'user allowed to see members' do + context 'if user is allowed to see members' do let(:project_permissions) { [:view_members] } it_behaves_like 'has a titled link' do let(:link) { 'memberships' } let(:href) { memberships_path } - let(:title) { I18n.t(:label_member_plural) } + let(:title) { I18n.t(:label_membership_plural) } end end - context 'user allowed to manage members' do + context 'if user is allowed to manage members' do let(:project_permissions) { [:manage_members] } it_behaves_like 'has a titled link' do let(:link) { 'memberships' } let(:href) { memberships_path } - let(:title) { I18n.t(:label_member_plural) } + let(:title) { I18n.t(:label_membership_plural) } end end end @@ -140,15 +140,15 @@ let(:value) { 'PlaceholderUser' } end - context 'as regular user' do - it_behaves_like 'property', :id do - let(:value) { placeholder_user.id } - end + it_behaves_like 'property', :id do + let(:value) { placeholder_user.id } + end - it_behaves_like 'property', :name do - let(:value) { placeholder_user.name } - end + it_behaves_like 'property', :name do + let(:value) { placeholder_user.name } + end + context 'as regular user' do it 'hides the updatedAt property' do expect(subject).not_to have_json_path('updatedAt') end @@ -158,15 +158,11 @@ end end - context 'as admin' do - let(:current_user) { build_stubbed(:admin) } + context 'if user is allowed to manage placeholder users' do + let(:global_permissions) { [:manage_placeholder_user] } - it_behaves_like 'property', :id do - let(:value) { placeholder_user.id } - end - - it_behaves_like 'property', :name do - let(:value) { placeholder_user.name } + it_behaves_like 'property', :status do + let(:value) { 'active' } end it_behaves_like 'datetime property', :createdAt do @@ -181,12 +177,15 @@ describe 'caching' do it 'is based on the representer\'s cache_key' do - expect(OpenProject::Cache) + allow(OpenProject::Cache) .to receive(:fetch) - .with(representer.json_cache_key) .and_call_original representer.to_json + + expect(OpenProject::Cache) + .to have_received(:fetch) + .with(representer.json_cache_key) end describe '#json_cache_key' do @@ -205,7 +204,7 @@ end it 'changes when the placeholder is updated' do - placeholder_user.updated_at = Time.now + 20.seconds + placeholder_user.updated_at = 20.seconds.from_now expect(representer.json_cache_key) .not_to eql former_cache_key diff --git a/spec/lib/api/v3/users/user_representer_spec.rb b/spec/lib/api/v3/users/user_representer_spec.rb index 877052a520a5..fe12cac792ad 100644 --- a/spec/lib/api/v3/users/user_representer_spec.rb +++ b/spec/lib/api/v3/users/user_representer_spec.rb @@ -36,7 +36,7 @@ include API::V3::Utilities::PathHelper - context 'generation' do + describe 'generation' do subject(:generated) { representer.to_json } it do @@ -114,13 +114,13 @@ end end - context 'user shows his E-Mail address' do + context 'if user shows his E-Mail address' do let(:preference) { build(:user_preference, hide_mail: false) } it_behaves_like 'shows the users E-Mail address' end - context 'user hides his E-Mail address' do + context 'if user hides his E-Mail address' do let(:preference) { build(:user_preference, hide_mail: true) } it 'does not render the users E-Mail address' do @@ -156,7 +156,7 @@ expect(subject).to have_json_path('_links/self/href') end - context 'showUser' do + describe 'showUser' do it_behaves_like 'has an untitled link' do let(:link) { 'showUser' } let(:href) { "/users/#{user.id}" } @@ -197,8 +197,8 @@ context 'when deletion is allowed' do before do allow(Users::DeleteContract).to receive(:deletion_allowed?) - .with(user, current_user) - .and_return(true) + .with(user, current_user) + .and_return(true) end it 'links to delete' do @@ -210,8 +210,8 @@ context 'when deletion is allowed' do before do allow(Users::DeleteContract).to receive(:deletion_allowed?) - .with(user, current_user) - .and_return(true) + .with(user, current_user) + .and_return(true) end it 'links to delete' do @@ -222,8 +222,8 @@ context 'when deletion is not allowed' do before do allow(Users::DeleteContract).to receive(:deletion_allowed?) - .with(user, current_user) - .and_return(false) + .with(user, current_user) + .and_return(false) end it 'does not link to delete' do @@ -252,7 +252,7 @@ it_behaves_like 'has a titled link' do let(:link) { 'memberships' } - let(:title) { I18n.t(:label_member_plural) } + let(:title) { I18n.t(:label_membership_plural) } end end @@ -261,7 +261,7 @@ it_behaves_like 'has a titled link' do let(:link) { 'memberships' } - let(:title) { I18n.t(:label_member_plural) } + let(:title) { I18n.t(:label_membership_plural) } end end @@ -277,12 +277,15 @@ describe 'caching' do it 'is based on the representer\'s cache_key' do - expect(OpenProject::Cache) + allow(OpenProject::Cache) .to receive(:fetch) - .with(representer.json_cache_key) - .and_call_original + .and_call_original representer.to_json + + expect(OpenProject::Cache) + .to have_received(:fetch) + .with(representer.json_cache_key) end describe '#json_cache_key' do @@ -308,7 +311,7 @@ end it 'changes when the user is updated' do - user.updated_at = Time.now + 20.seconds + user.updated_at = 20.seconds.from_now expect(representer.json_cache_key) .not_to eql former_cache_key diff --git a/spec/requests/api/v3/placeholder_users/delete_resource_examples.rb b/spec/requests/api/v3/placeholder_users/delete_resource_examples.rb index 37e5f64fa4b4..41478c989d39 100644 --- a/spec/requests/api/v3/placeholder_users/delete_resource_examples.rb +++ b/spec/requests/api/v3/placeholder_users/delete_resource_examples.rb @@ -51,6 +51,6 @@ end it 'does not delete the user' do - expect(PlaceholderUser.exists?(placeholder.id)).to be_truthy + expect(PlaceholderUser).to exist(placeholder.id) end end