Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENG-30: add list of users in organisation #41

Merged
merged 1 commit into from
Aug 20, 2024
Merged
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
8 changes: 4 additions & 4 deletions backend/app/repos/user_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ def get_by_slack_user_id(self, slack_user_id: str) -> User | None:
query = select(User).where(User.slack_user_id == slack_user_id).limit(1)
return self.session.scalars(query).first()

def get_all_users_in_organisation(self, organisation: Organisation) -> Sequence[User]:
"""Get all users within organisation"""
def get_all_organisation_members(self, organisation: Organisation) -> Sequence[OrganisationMember]:
"""Get all members of an organisation"""
stmt = (
select(User)
.join(OrganisationMember)
select(OrganisationMember)
.join(User)
.where(OrganisationMember.organisation_id == organisation.id, User.is_active.is_(True))
)

Expand Down
10 changes: 5 additions & 5 deletions backend/app/routes/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from app.exceptions import ErrorCodes, FormFieldValidationError, ValidationError
from app.repos import OrganisationRepo, UserRepo
from app.schemas.actions import AuthUserSchema, CreateUserSchema, SendVerificationEmailSchema, VerifyEmailSchema
from app.schemas.models import UserPublicSchema, UserSchema
from app.schemas.models import OrganisationMemberSchema, UserSchema
from app.schemas.resources import PaginatedResults
from app.schemas.tasks import SendVerificationEmailParameters
from app.services.factories import create_onboarding_service
Expand Down Expand Up @@ -76,15 +76,15 @@ def me(
return user


@router.get("/search", response_model=PaginatedResults[UserPublicSchema])
@router.get("/search", response_model=PaginatedResults[OrganisationMemberSchema])
def users_search(user: CurrentUser, db: DatabaseSession, organisation: CurrentOrganisation):
"""Get all users in the organisation"""
user_repo = UserRepo(session=db)

users = user_repo.get_all_users_in_organisation(organisation=organisation)
total = len(users)
results = user_repo.get_all_organisation_members(organisation=organisation)
total = len(results)

return PaginatedResults(total=total, page=1, size=total, items=users)
return PaginatedResults(total=total, page=1, size=total, items=results)


@router.post("/verify")
Expand Down
5 changes: 5 additions & 0 deletions backend/app/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,9 @@ class LifecycleSchema(ModelSchema):
is_triage_available: bool


class OrganisationMemberSchema(ModelSchema):
user: PublicUserSchema
role: str


IncidentTypeSchema.model_rebuild()
4 changes: 3 additions & 1 deletion backend/app/services/onboarding.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def _setup_incident_roles(self, organisation: Organisation) -> None:
"description": "The user responsible for first reporting the incident",
"slack_reference": "reporter",
"is_deletable": False,
"is_editable": False,
"is_editable": True,
},
{
"name": "Incident Lead",
Expand All @@ -252,6 +252,8 @@ def _setup_incident_roles(self, organisation: Organisation) -> None:
description=item["description"],
kind=item["kind"],
slack_reference=item["slack_reference"],
is_deletable=item["is_deletable"],
is_editable=item["is_editable"],
)

def _setup_announcement(self, organisation: Organisation) -> None:
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import OAuthComplete from '@/pages/OAuth/Complete'
import SettingsForms from '@/pages/Settings/components/Forms/Forms'
import SettingsFields from '@/pages/Settings/Fields'
import SettingsIndex from '@/pages/Settings/Index'
import SettingsMembers from '@/pages/Settings/Members'
import SettingsRoles from '@/pages/Settings/Roles'
import SettingsSeverity from '@/pages/Settings/Severity'
import SettingsSlack from '@/pages/Settings/Slack'
Expand Down Expand Up @@ -95,6 +96,7 @@ const App = () => {
<Route path={RoutePaths.SETTINGS_TYPES} element={<SettingsIncidentTypes />} />
<Route path={RoutePaths.SETTINGS_FORMS_INDEX} element={<SettingsForms />} />
<Route path={RoutePaths.SETTINGS_STATUSES} element={<SettingsStatuses />} />
<Route path={RoutePaths.SETTINGS_USERS} element={<SettingsMembers />} />
</Route>
</Route>
<Route path="*" element={<PageNotFound />} />
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/Sidebar/SettingsSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const SettingsSidebar: React.FC<Props> = () => {
<MenuItem to={RoutePaths.SETTINGS_STATUSES}>Status</MenuItem>
<MenuItem to={RoutePaths.SETTINGS_TIMESTAMPS}>Timestamps</MenuItem>
<MenuItem to={RoutePaths.SETTINGS_TYPES}>Types</MenuItem>
<MenuItem to={RoutePaths.SETTINGS_USERS}>Users</MenuItem>
</SubItems>
</SubMenu>

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/Incidents/Show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ const ShowIncident = () => {
// Fetch roles available for the organisation
const usersQuery = useQuery({
queryKey: ['users', organisation!.id],
queryFn: () => apiService.getUsers()
queryFn: () => apiService.getOrganisationMembers()
})

// Fetch incident field values
Expand Down
86 changes: 86 additions & 0 deletions frontend/src/pages/Settings/Members.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useQuery } from '@tanstack/react-query'
import { format } from 'date-fns/format'
import { useMemo } from 'react'
import styled from 'styled-components'

import Table, { ColumnProperty } from '@/components/Table/Table'
import { Box, Content, ContentMain, Header, Title } from '@/components/Theme/Styles'
import useApiService from '@/hooks/useApi'
import useGlobal from '@/hooks/useGlobal'
import { IOrganisationMember } from '@/types/models'

const Intro = styled.div`
padding: 1rem;
`
const Name = styled.div`
display: flex;
gap: 1rem;
align-items: center;
`
const Controls = styled.div`
display: flex;
width: 100%;

> div {
margin-left: auto;
display: flex;
gap: 0.5rem;
}
`

const SettingsMembers = () => {
const { apiService } = useApiService()
const { organisation } = useGlobal()

const usersQuery = useQuery({
queryKey: [organisation?.id, 'members'],
queryFn: () => apiService.getOrganisationMembers()
})

const columns = useMemo(
() =>
[
{
name: 'Name',
render: (v) => <Name>{v.user.name}</Name>
},
{
name: 'Email',
render: (v) => v.user.emailAddress
},
{
name: 'Role',
render: (v) => v.role
},
{
name: 'Created',
render: (v) => format(v.createdAt, 'do MMMM yyyy')
},
{
name: '',
render: () => <Controls></Controls>
}
] as ColumnProperty<IOrganisationMember>[],
[]
)

return (
<>
<Box>
<Header>
<Title>Manage users</Title>
</Header>
<Content>
<ContentMain $padding={false}>
<Intro>
<p>Shown below are the members of your organisation</p>
</Intro>
{usersQuery.isSuccess ? <Table data={usersQuery.data.items} rowKey={'id'} columns={columns} /> : null}
</ContentMain>
</Content>
</Box>
</>
)
}

export default SettingsMembers
1 change: 1 addition & 0 deletions frontend/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum RoutePaths {
SETTINGS_TYPES = '/:organisation/settings/types',
SETTINGS_STATUSES = '/:organisation/settings/statuses',
SETTINGS_FORMS_INDEX = '/:organisation/settings/forms',
SETTINGS_USERS = '/:organisation/settings/users',

INCIDENTS = '/incidents',
SHOW_INCIDENT = '/incidents/:id',
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ILifecycle,
ILoggedInUser,
IOrganisation,
IOrganisationMember,
IPublicUser,
ISettings,
ITimestamp,
Expand Down Expand Up @@ -229,8 +230,8 @@ export class ApiService {
})
}

getUsers = () => {
return callApi<PaginatedResults<IPublicUser>>('GET', `/users/search`, {
getOrganisationMembers = () => {
return callApi<PaginatedResults<IOrganisationMember>>('GET', `/users/search`, {
user: this.user,
headers: {
[ORGANISATION_HEADER_KEY]: this.organisation
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/types/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ export enum FieldInterfaceKind {
TEXT = 'TEXT',
TEXTAREA = 'TEXTAREA'
}

export enum MemberRole {
MEMBER = 'MEMBER'
}
6 changes: 6 additions & 0 deletions frontend/src/types/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FormType,
IncidentRoleKind,
IncidentStatusCategory,
MemberRole,
OrganisationKind
} from './enums'

Expand Down Expand Up @@ -179,3 +180,8 @@ export interface IIncidentFieldValue extends IModel {
export interface ILifecycle extends IModel {
isTriageAvailable: boolean
}

export interface IOrganisationMember extends IModel {
user: IPublicUser
role: MemberRole
}
Loading