Repository navigation
feat(people): organization People page with profiles, reporting lines, and people-data import #9517
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
dd4b2b3
feat(people): organization People page with profiles, reporting lines…
waleedlatif1 6513119
fix(people): clearable manager and department pickers, exact spreadsh…
waleedlatif1 62af64f
fix(people): consistent profile reads, capability-aware navigation, u…
waleedlatif1 4b77caf
fix(people): read a page and its managers from one snapshot
waleedlatif1 c9e655a
fix(people): send import rows as positional cells so wide files stay …
waleedlatif1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| --- | ||
| title: People | ||
| description: Your organization's people, with titles, departments, locations, and who reports to whom | ||
| --- | ||
|
|
||
| **People** in the organization sidebar lists everyone active in your organization. Search by name, email, title, department, or location, or filter by department. Select a person to see their profile, the managers above them, and the people who report to them. | ||
|
|
||
| Organization admins keep this information up to date by importing it from your HR system or editing a profile. | ||
|
|
||
| ## Import people data | ||
|
|
||
| 1. Export your people from your HR system as CSV or Excel. | ||
| 2. Open **People** and select **Import**, then choose the file. | ||
| 3. Review the preview, then select **Import**. | ||
|
|
||
| Each row is matched to a member by email. The file needs an `email` column and any of: | ||
|
|
||
| | Column | Also recognized as | | ||
| | --- | --- | | ||
| | `title` | `job title`, `position` | | ||
| | `department` | `dept` | | ||
| | `location` | `office`; or `city`, `state`, and `country`, which are combined | | ||
| | `start_date` | `hire date`. Use `2024-01-31` or `1/31/2024`. | | ||
| | `manager_email` | `manager`, `reports to`, `supervisor`. Use the manager's email. | | ||
|
|
||
| **Download template** in the import dialog gives a file with these columns. | ||
|
|
||
| - A column the file includes replaces that field for every listed person; a blank cell clears it. A column the file leaves out is not changed. | ||
| - Rows are skipped when the person isn't a member, the email or date is invalid, a title, department, or location is over 200 characters, the person is named as their own manager, or an earlier row already updated the same person. The preview lists the first 200 issues with their row numbers. | ||
| - A manager who isn't a member, or who would make a reporting loop, is left as it was, and the preview says so. | ||
| - Nothing changes until you confirm the preview. Import up to 10,000 rows at a time. | ||
|
|
||
| ## Edit a profile | ||
|
|
||
| Organization admins can open a person and select **Edit** to change their title, department, location, start date, or manager. | ||
|
|
||
| When someone leaves your organization, their profile is removed, and anyone who reported to them no longer has a manager recorded. | ||
|
|
||
| Imports and edits are recorded in your organization's [audit logs](/platform/enterprise/audit-logs). | ||
|
|
||
| ## Related docs | ||
|
|
||
| - [Sim Links](/search/sim-links) |
38 changes: 38 additions & 0 deletions
38
apps/sim/app/api/organizations/[id]/people/[memberId]/route.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { getPersonContract, updatePersonContract } from '@/lib/api/contracts/people' | ||
| import { presentPerson } from '@/lib/api/server/people-presenters' | ||
| import { | ||
| defineInternalJsonRoute, | ||
| internalOrchestrationErrorPolicy, | ||
| internalRateLimits, | ||
| internalSessionAuth, | ||
| } from '@/lib/api/server/routes' | ||
| import { getPerson, updatePerson } from '@/lib/people/application/use-cases' | ||
|
|
||
| const policy = { | ||
| auth: internalSessionAuth, | ||
| rateLimit: internalRateLimits.user({ bucketName: 'people' }), | ||
| errorPolicy: internalOrchestrationErrorPolicy, | ||
| staticResponseHeaders: { 'Cache-Control': 'private, no-store' }, | ||
| } | ||
|
|
||
| export const GET = defineInternalJsonRoute({ | ||
| ...policy, | ||
| contract: getPersonContract, | ||
| operation: getPerson.operation, | ||
| useCase: getPerson, | ||
| mapInput: ({ params }) => ({ organizationId: params.id, memberId: params.memberId }), | ||
| present: ({ person, chain, reports }) => ({ person: presentPerson(person), chain, reports }), | ||
| }) | ||
|
|
||
| export const PUT = defineInternalJsonRoute({ | ||
| ...policy, | ||
| contract: updatePersonContract, | ||
| operation: updatePerson.operation, | ||
| useCase: updatePerson, | ||
| mapInput: ({ params, body }) => ({ | ||
| organizationId: params.id, | ||
| memberId: params.memberId, | ||
| ...body, | ||
| }), | ||
| present: (person) => ({ person: presentPerson(person) }), | ||
| }) |
20 changes: 20 additions & 0 deletions
20
apps/sim/app/api/organizations/[id]/people/departments/route.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { listDepartmentsContract } from '@/lib/api/contracts/people' | ||
| import { | ||
| defineInternalJsonRoute, | ||
| internalOrchestrationErrorPolicy, | ||
| internalRateLimits, | ||
| internalSessionAuth, | ||
| } from '@/lib/api/server/routes' | ||
| import { listDepartments } from '@/lib/people/application/use-cases' | ||
|
|
||
| export const GET = defineInternalJsonRoute({ | ||
| contract: listDepartmentsContract, | ||
| operation: listDepartments.operation, | ||
| useCase: listDepartments, | ||
| auth: internalSessionAuth, | ||
| rateLimit: internalRateLimits.user({ bucketName: 'people' }), | ||
| errorPolicy: internalOrchestrationErrorPolicy, | ||
| staticResponseHeaders: { 'Cache-Control': 'private, no-store' }, | ||
| mapInput: ({ params }) => ({ organizationId: params.id }), | ||
| present: (departments) => ({ departments }), | ||
| }) |
20 changes: 20 additions & 0 deletions
20
apps/sim/app/api/organizations/[id]/people/import/route.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { importPeopleContract } from '@/lib/api/contracts/people' | ||
| import { | ||
| defineInternalJsonRoute, | ||
| internalOrchestrationErrorPolicy, | ||
| internalRateLimits, | ||
| internalSessionAuth, | ||
| } from '@/lib/api/server/routes' | ||
| import { importPeople } from '@/lib/people/application/use-cases' | ||
|
|
||
| export const POST = defineInternalJsonRoute({ | ||
| contract: importPeopleContract, | ||
| operation: importPeople.operation, | ||
| useCase: importPeople, | ||
| auth: internalSessionAuth, | ||
| rateLimit: internalRateLimits.user({ bucketName: 'people-import' }), | ||
| errorPolicy: internalOrchestrationErrorPolicy, | ||
| staticResponseHeaders: { 'Cache-Control': 'private, no-store' }, | ||
| mapInput: ({ params, body }) => ({ organizationId: params.id, ...body }), | ||
| present: (result) => result, | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { listPeopleContract } from '@/lib/api/contracts/people' | ||
| import { cursorRoute, cursorScopeKey } from '@/lib/api/cursor-binding' | ||
| import { presentPerson } from '@/lib/api/server/people-presenters' | ||
| import { | ||
| defineInternalJsonRoute, | ||
| internalOrchestrationErrorPolicy, | ||
| internalRateLimits, | ||
| internalSessionAuth, | ||
| } from '@/lib/api/server/routes' | ||
| import { listPeople } from '@/lib/people/application/use-cases' | ||
| import { readSortedCursor, writeSortedCursor } from '@/app/api/v2/lib/response' | ||
|
|
||
| /** A cursor resumes only the list it came from: the same organization, search, and department. */ | ||
| function cursorScope(id: string, search: string | undefined, department: string | undefined) { | ||
| return cursorScopeKey(cursorRoute(listPeopleContract, { id }), { search, department }) | ||
| } | ||
|
|
||
| export const GET = defineInternalJsonRoute({ | ||
| contract: listPeopleContract, | ||
| operation: listPeople.operation, | ||
| useCase: listPeople, | ||
| auth: internalSessionAuth, | ||
| rateLimit: internalRateLimits.user({ bucketName: 'people' }), | ||
| errorPolicy: internalOrchestrationErrorPolicy, | ||
| staticResponseHeaders: { 'Cache-Control': 'private, no-store' }, | ||
| mapInput: ({ params, query }) => ({ | ||
| organizationId: params.id, | ||
| search: query.search, | ||
| department: query.department, | ||
| cursorKeys: readSortedCursor( | ||
| query.cursor, | ||
| 'name', | ||
| 'asc', | ||
| cursorScope(params.id, query.search, query.department) | ||
| ), | ||
| }), | ||
| present: ({ data, nextCursorKeys }, { input }) => ({ | ||
| people: data.map(presentPerson), | ||
| nextCursor: writeSortedCursor( | ||
| nextCursorKeys, | ||
| 'name', | ||
| 'asc', | ||
| cursorScope(input.organizationId, input.search, input.department) | ||
| ), | ||
| }), | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
apps/sim/app/o/[organizationId]/people/[memberId]/loading.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { ApplicationLoading } from '@/app/_shell/application-loading' | ||
|
|
||
| export default function Loading() { | ||
| return <ApplicationLoading /> | ||
| } |
27 changes: 27 additions & 0 deletions
27
apps/sim/app/o/[organizationId]/people/[memberId]/page.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import type { Metadata } from 'next' | ||
| import { notFound, redirect } from 'next/navigation' | ||
| import { getSession } from '@/lib/auth' | ||
| import { organizationRoutes } from '@/lib/navigation/paths' | ||
| import { isPeopleEnabled } from '@/lib/people/feature-flag' | ||
| import { buildAuthCrossLink } from '@/app/(auth)/auth-redirect' | ||
| import { Person } from '@/app/o/[organizationId]/people/[memberId]/person' | ||
|
|
||
| export const metadata: Metadata = { title: 'People' } | ||
|
|
||
| interface PersonPageProps { | ||
| params: Promise<{ organizationId: string; memberId: string }> | ||
| } | ||
|
|
||
| export default async function PersonPage({ params }: PersonPageProps) { | ||
| const { organizationId, memberId } = await params | ||
| const session = await getSession() | ||
| if (!session?.user) | ||
|
waleedlatif1 marked this conversation as resolved.
Resolved
|
||
| redirect( | ||
| buildAuthCrossLink('/login', { | ||
| callbackUrl: organizationRoutes(organizationId).person(memberId), | ||
| isInviteFlow: false, | ||
| }) | ||
| ) | ||
| if (!(await isPeopleEnabled(organizationId))) notFound() | ||
| return <Person organizationId={organizationId} memberId={memberId} /> | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.