Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions src/UnisonShare/AddOrgMemberModal.elm
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,10 @@ update appContext orgHandle currentMembers msg model =
userHandles m =
case m of
OrgMember.UserMember { user } ->
Just user.handle

_ ->
Nothing
user.handle

usersToIgnore =
account.handle :: List.filterMap userHandles currentMembers
account.handle :: List.map userHandles currentMembers

model_ =
if Search.queryEquals q search then
Expand Down Expand Up @@ -158,7 +155,7 @@ update appContext orgHandle currentMembers msg model =
AddMemberFinished res ->
case ( model, res ) of
( Saving member, Ok _ ) ->
( Success member, Cmd.none, AddedMember (OrgMember.UserMember { user = member.user, roles = [ member.role ] }) )
( Success member, Cmd.none, AddedMember (OrgMember.UserMember { user = member.user, role = member.role }) )

( Saving member, Err e ) ->
( Failure e member, Cmd.none, NoOutMsg )
Expand Down Expand Up @@ -197,7 +194,7 @@ fetchUsers appContext query =

addMember : AppContext -> UserHandle -> PotentialOrgMember -> Cmd Msg
addMember appContext orgHandle member =
ShareApi.createOrgRoleAssignment orgHandle [ OrgMember.UserMember { user = member.user, roles = [ member.role ] } ]
ShareApi.createOrgRoleMember orgHandle [ OrgMember.UserMember { user = member.user, role = member.role } ]
|> HttpApi.toRequestWithEmptyResponse AddMemberFinished
|> HttpApi.perform appContext.api

Expand Down
67 changes: 16 additions & 51 deletions src/UnisonShare/Api.elm
Original file line number Diff line number Diff line change
Expand Up @@ -251,95 +251,60 @@ createOrg owner name orgHandle isCommercial =
}


orgRoleAssignments : UserHandle -> Endpoint
orgRoleAssignments orgHandle =
orgRoleMembers : UserHandle -> Endpoint
orgRoleMembers orgHandle =
let
handle =
UserHandle.toUnprefixedString orgHandle
in
GET
{ path = [ "orgs", handle, "roles" ]
{ path = [ "orgs", handle, "members" ]
, queryParams = []
}


createOrgRoleAssignment : UserHandle -> List OrgMember -> Endpoint
createOrgRoleAssignment orgHandle members =
createOrgRoleMember : UserHandle -> List OrgMember -> Endpoint
createOrgRoleMember orgHandle members =
let
handle =
UserHandle.toUnprefixedString orgHandle

toAssignment member =
encodeMember member =
case member of
OrgMember.UserMember u ->
Encode.object
[ ( "subject"
, Encode.object
[ ( "kind", Encode.string "user" )
, ( "id", Encode.string u.user.id )
]
)
, ( "roles", Encode.list OrgRole.encode u.roles )
]

OrgMember.TeamMember t ->
Encode.object
[ ( "subject"
, Encode.object
[ ( "kind", Encode.string "team" )
, ( "id", Encode.string t.teamId )
]
)
, ( "roles", Encode.list OrgRole.encode t.roles )
[ ( "subject", Encode.string (UserHandle.toUnprefixedString u.user.handle) )
, ( "role", OrgRole.encode u.role )
]

body =
Encode.object
[ ( "role_assignments", Encode.list toAssignment members ) ]
[ ( "members", Encode.list encodeMember members ) ]
in
POST
{ path = [ "orgs", handle, "roles" ]
{ path = [ "orgs", handle, "members" ]
, queryParams = []
, body = Http.jsonBody body
}


deleteOrgRoleAssignment : UserHandle -> OrgMember -> Endpoint
deleteOrgRoleAssignment orgHandle member =
deleteOrgRoleMember : UserHandle -> OrgMember -> Endpoint
deleteOrgRoleMember orgHandle member =
let
handle =
UserHandle.toUnprefixedString orgHandle

toAssignment member_ =
toMember member_ =
case member_ of
OrgMember.UserMember u ->
Encode.object
[ ( "subject"
, Encode.object
[ ( "kind", Encode.string "user" )
, ( "id", Encode.string u.user.id )
]
)
, ( "roles", Encode.list OrgRole.encode u.roles )
]

OrgMember.TeamMember t ->
Encode.object
[ ( "subject"
, Encode.object
[ ( "kind", Encode.string "team" )
, ( "id", Encode.string t.teamId )
]
)
, ( "roles", Encode.list OrgRole.encode t.roles )
]
Encode.string (UserHandle.toUnprefixedString u.user.handle)

body =
Encode.object
[ ( "role_assignments", Encode.list toAssignment [ member ] ) ]
[ ( "members", Encode.list toMember [ member ] ) ]
in
DELETE
{ path = [ "orgs", handle, "roles" ]
{ path = [ "orgs", handle, "members" ]
, queryParams = []
, body = Http.jsonBody body
}
Expand Down
35 changes: 10 additions & 25 deletions src/UnisonShare/OrgMember.elm
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,27 @@ module UnisonShare.OrgMember exposing (..)

import Json.Decode as Decode exposing (string)
import Json.Decode.Extra exposing (when)
import Json.Decode.Pipeline exposing (required, requiredAt)
import Json.Decode.Pipeline exposing (required)
import Lib.Decode.Helpers exposing (failInvalid)
import UnisonShare.OrgRole as OrgRole exposing (OrgRole)
import UnisonShare.User as User exposing (UserSummaryWithId)


type OrgMember
= UserMember { roles : List OrgRole, user : UserSummaryWithId }
| TeamMember { roles : List OrgRole, teamId : String }
type
OrgMember
-- | TeamMember { role : OrgRole, teamId : String }
= UserMember { role : OrgRole, user : UserSummaryWithId }


decodeOrgUserMember : Decode.Decoder OrgMember
decodeOrgUserMember =
let
make roles user =
UserMember { roles = roles, user = user }
make role user =
UserMember { role = role, user = user }
in
Decode.succeed make
|> required "roles" (Decode.list OrgRole.decode)
|> requiredAt [ "subject", "data" ] User.decodeSummaryWithId


decodeOrgTeamMember : Decode.Decoder OrgMember
decodeOrgTeamMember =
let
make roles teamId =
TeamMember { roles = roles, teamId = teamId }
in
Decode.succeed make
|> required "roles" (Decode.list OrgRole.decode)
|> requiredAt [ "subject", "data", "teamId" ] Decode.string
|> required "role" OrgRole.decode
|> required "subject" User.decodeSummaryWithId


whenPathIs : List String -> String -> Decode.Decoder a -> Decode.Decoder a
Expand All @@ -42,13 +32,8 @@ whenPathIs path val =

decodeMaybe : Decode.Decoder (Maybe OrgMember)
decodeMaybe =
let
whenKindIs kind =
whenPathIs [ "subject", "kind" ] kind
in
Decode.oneOf
[ whenKindIs "user" (Decode.map Just decodeOrgUserMember)
, whenKindIs "team" (Decode.map Just decodeOrgTeamMember)
[ Decode.map Just decodeOrgUserMember
, Decode.succeed Nothing
]

Expand Down
21 changes: 7 additions & 14 deletions src/UnisonShare/Page/OrgPeoplePage.elm
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,6 @@ update appContext orgHandle msg model =
OrgMember.UserMember { user } ->
not (UserHandle.equals user.handle handle)

_ ->
True

members =
RemoteData.map
(List.filter withoutRemovedMember)
Expand Down Expand Up @@ -180,16 +177,16 @@ update appContext orgHandle msg model =

fetchMembers : AppContext -> UserHandle -> Cmd Msg
fetchMembers appContext orgHandle =
ShareApi.orgRoleAssignments orgHandle
ShareApi.orgRoleMembers orgHandle
|> HttpApi.toRequest
(Decode.field "role_assignments" OrgMember.decodeList)
(Decode.field "members" OrgMember.decodeList)
(RemoteData.fromResult >> FetchMembersFinished)
|> HttpApi.perform appContext.api


removeMember : AppContext -> UserHandle -> UserHandle -> OrgMember -> Cmd Msg
removeMember appContext orgHandle memberHandle member =
ShareApi.deleteOrgRoleAssignment orgHandle member
ShareApi.deleteOrgRoleMember orgHandle member
|> HttpApi.toRequestWithEmptyResponse (RemoveMemberFinished memberHandle)
|> HttpApi.perform appContext.api

Expand All @@ -206,11 +203,11 @@ viewRole role =
|> Tooltip.view (role |> OrgRole.toString |> text)


viewUserMember : ConfirmDeletes -> Bool -> { user : UserSummaryWithId, roles : List OrgRole } -> Html Msg
viewUserMember deletes isLastUser ({ user, roles } as member) =
viewUserMember : ConfirmDeletes -> Bool -> { user : UserSummaryWithId, role : OrgRole } -> Html Msg
viewUserMember deletes isLastUser ({ user, role } as member) =
let
canRemove =
not (List.member OrgRole.Owner roles) && not isLastUser
OrgRole.Owner /= role && not isLastUser

remove =
if canRemove then
Expand All @@ -236,7 +233,7 @@ viewUserMember deletes isLastUser ({ user, roles } as member) =
div [ class "member" ]
[ div [ class "member_profile-snippet" ]
[ ProfileSnippet.profileSnippet user |> ProfileSnippet.view ]
, div [ class "member_role" ] (roles |> List.map viewRole |> List.intersperse (text ", "))
, div [ class "member_role" ] [ viewRole role ]
, div [ class "member_remove" ] [ remove ]
]

Expand All @@ -247,10 +244,6 @@ viewMember deletes isLastUser member =
OrgMember.UserMember m ->
viewUserMember deletes isLastUser m

OrgMember.TeamMember _ ->
-- TODO: Teams are not yet fully supported
UI.nothing


viewContent : Model -> List (Html Msg)
viewContent model =
Expand Down
11 changes: 11 additions & 0 deletions src/css/confirm-delete.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,14 @@
font-size: var(--font-size-small);
align-items: center;
}

.confirm-delete .status-banner {
font-size: var(--font-size-small);
gap: 0.25rem;
padding: 0.2rem 0.5rem;
padding-left: 0.25rem;
}
.confirm-delete .status-indicator.status-indicator_regular {
--c-size_status-indicator: 1rem;
--c-size_status-indicator_icon: 0.75rem;
}
10 changes: 3 additions & 7 deletions src/css/unison-share/page/org-people-page.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
.org-people-page .member_profile-snippet {
width: 50%;
}
.org-people-page .member_role,

.org-people-page .member_remove {
width: 25%;
width: 21rem;
}

.org-people-page .member_role {
Expand Down Expand Up @@ -87,13 +87,9 @@
width: 8rem;
text-align: right;
}

.org-people-page .member_remove {
width: 2rem;
}
}

@container org-people-members (max-width: 500px) {
@container org-people-members (max-width: 750px) {
.org-people-page .members_list {
gap: 2rem;
}
Expand Down
6 changes: 3 additions & 3 deletions tests/e2e/OrgPeoplePage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test.describe("without being signed in", () => {
test("can *NOT* view the org people page", async ({ page }) => {
const orgHandle = "@unison";
await API.getOrgProfile(page, orgHandle);
await API.getOrgRoleAssignments_(page, orgHandle, { status: 403 });
await API.getOrgRoleMembers_(page, orgHandle, { status: 403 });

const response = await page.goto(
`http://localhost:1234/${orgHandle}/p/people`,
Expand All @@ -37,7 +37,7 @@ test.describe("without org:manage permission", () => {
test("can *NOT* view the org people page", async ({ page }) => {
const orgHandle = "@unison";
await API.getOrgProfile(page, orgHandle, { permissions: ["org:view"] });
await API.getOrgRoleAssignments_(page, orgHandle, { status: 403 });
await API.getOrgRoleMembers_(page, orgHandle, { status: 403 });

const response = await page.goto(
`http://localhost:1234/${orgHandle}/p/people`,
Expand All @@ -60,7 +60,7 @@ test.describe("with org:manage permission", () => {
test("can view the org people page", async ({ page }) => {
const orgHandle = "@unison";
await API.getOrgProfile(page, orgHandle, { permissions: ["org:manage"] });
await API.getOrgRoleAssignments(page, orgHandle);
await API.getOrgRoleMembers(page, orgHandle);

const response = await page.goto(
`http://localhost:1234/${orgHandle}/p/people`,
Expand Down
Loading
Loading