Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ describe('SessionsSidebar', () => {
render(<SessionsSidebar {...defaultProps} />);
expect(screen.getByText('Sessions')).toBeDefined();
expect(screen.getByText('Schedules')).toBeDefined();
expect(screen.getByText('Sharing')).toBeDefined();
expect(screen.getByText('Pair Prompting')).toBeDefined();
expect(screen.getByText('Access Keys')).toBeDefined();
expect(screen.getByText('Workspace Settings')).toBeDefined();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export function SessionsSidebar({
href: `/projects/${projectName}/scheduled-sessions`,
},
{
label: "Sharing",
label: "Pair Prompting",
icon: Share2,
href: `/projects/${projectName}/permissions`,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('SharingSection', () => {

it('renders permissions table with data', () => {
render(<SharingSection projectName="test-project" />);
expect(screen.getByText('Sharing')).toBeDefined();
expect(screen.getByText('Pair Prompting')).toBeDefined();
expect(screen.getByText('developers')).toBeDefined();
expect(screen.getByText('alice')).toBeDefined();
});
Expand All @@ -87,7 +87,7 @@ describe('SharingSection', () => {
render(<SharingSection projectName="test-project" />);
fireEvent.click(screen.getByText('Grant Permission'));

const nameInput = screen.getByPlaceholderText('Enter group name');
const nameInput = screen.getByPlaceholderText('e.g., platform-team');
fireEvent.change(nameInput, { target: { value: 'new-team' } });

// Click the Grant Permission button in the dialog footer
Expand Down Expand Up @@ -150,5 +150,6 @@ describe('SharingSection', () => {

render(<SharingSection projectName="test-project" />);
expect(screen.getByText('No users or groups have access yet')).toBeDefined();
expect(screen.queryByText('Grant First Permission')).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { useCallback, useMemo, useState } from 'react';
import { Users, User as UserIcon, Plus, Loader2, Trash2, Info } from 'lucide-react';
import { Users, User as UserIcon, Plus, Loader2, Trash2, Info, HelpCircle } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
Expand All @@ -12,6 +12,7 @@ import { Label } from '@/components/ui/label';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { DestructiveConfirmationDialog } from '@/components/confirmation-dialog';

import { useProjectPermissions, useAddProjectPermission, useRemoveProjectPermission } from '@/services/queries';
Expand Down Expand Up @@ -50,6 +51,8 @@ export function SharingSection({ projectName }: SharingSectionProps) {

const isAdmin = userRole === 'admin' || userRole === undefined;

const subjectPlaceholder = grantForm.subjectType === 'group' ? 'e.g., platform-team' : 'e.g., jdoe@example.com';

const handleGrant = useCallback(() => {
if (!grantForm.subjectName.trim()) {
setGrantError(`${grantForm.subjectType === 'group' ? 'Group' : 'User'} name is required`);
Expand Down Expand Up @@ -113,16 +116,10 @@ export function SharingSection({ projectName }: SharingSectionProps) {
() => (
<div className="text-center py-8">
<Users className="w-8 h-8 text-muted-foreground mx-auto mb-2" />
<p className="text-sm text-muted-foreground mb-4">No users or groups have access yet</p>
{isAdmin && (
<Button onClick={() => setShowGrantDialog(true)} size="sm">
<Plus className="w-4 h-4 mr-2" />
Grant First Permission
</Button>
)}
<p className="text-sm text-muted-foreground">No users or groups have access yet</p>
</div>
),
[isAdmin]
[]
);

return (
Expand All @@ -143,7 +140,7 @@ export function SharingSection({ projectName }: SharingSectionProps) {
<div className="flex items-start justify-between">
<div>
<CardTitle>
Sharing
Pair Prompting
</CardTitle>
<CardDescription>Users and groups with access to this workspace and their roles</CardDescription>
</div>
Expand Down Expand Up @@ -227,9 +224,9 @@ export function SharingSection({ projectName }: SharingSectionProps) {
</CardContent>
</Card>

{/* Grant Permission Dialog */}
<Dialog open={showGrantDialog} onOpenChange={setShowGrantDialog}>
<DialogContent>
<TooltipProvider>
<DialogHeader>
<DialogTitle>Grant Permission</DialogTitle>
<DialogDescription>Add a user or group to this workspace with a role</DialogDescription>
Expand All @@ -251,22 +248,36 @@ export function SharingSection({ projectName }: SharingSectionProps) {
</Tabs>
</div>
<div className="space-y-2">
<Label htmlFor="subjectName">
{grantForm.subjectType === 'group' ? 'Group' : 'User'} Name
</Label>
<div className="flex items-center gap-1.5">
<Label htmlFor="subjectName">
{grantForm.subjectType === 'group' ? 'Group' : 'User'} Name
</Label>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="w-3.5 h-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent side="right" className="max-w-xs">
{grantForm.subjectType === 'group' ? (
<p>Enter the LDAP or identity provider group name, e.g. <code className="text-xs bg-muted px-1 rounded">platform-team</code>, <code className="text-xs bg-muted px-1 rounded">ai-engineering</code></p>
) : (
<p>Enter the username or email, e.g. <code className="text-xs bg-muted px-1 rounded">jdoe</code>, <code className="text-xs bg-muted px-1 rounded">jdoe@example.com</code></p>
)}
</TooltipContent>
</Tooltip>
</div>
{ldapEnabled ? (
<LDAPAutocomplete
id="subjectName"
mode={grantForm.subjectType}
placeholder={`Enter ${grantForm.subjectType} name`}
placeholder={subjectPlaceholder}
value={grantForm.subjectName}
onChange={(val) => setGrantForm((prev) => ({ ...prev, subjectName: val }))}
disabled={addPermissionMutation.isPending}
/>
) : (
<Input
id="subjectName"
placeholder={`Enter ${grantForm.subjectType} name`}
placeholder={subjectPlaceholder}
value={grantForm.subjectName}
onChange={(e) => setGrantForm((prev) => ({ ...prev, subjectName: e.target.value }))}
disabled={addPermissionMutation.isPending}
Expand Down Expand Up @@ -327,10 +338,10 @@ export function SharingSection({ projectName }: SharingSectionProps) {
)}
</Button>
</DialogFooter>
</TooltipProvider>
</DialogContent>
</Dialog>

{/* Revoke Permission Dialog */}
<DestructiveConfirmationDialog
open={showRevokeDialog}
onOpenChange={setShowRevokeDialog}
Expand Down
2 changes: 1 addition & 1 deletion e2e/cypress/e2e/screenshots.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ function runSetupStep(step: string): void {
cy.wait(500)
break
case 'navigateToSharing':
cy.contains('Sharing', { timeout: 5000 }).click()
cy.contains('Pair Prompting', { timeout: 5000 }).click()
cy.wait(500)
break
case 'waitForThemeToggle':
Expand Down
14 changes: 7 additions & 7 deletions e2e/cypress/e2e/sessions.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,9 @@ describe('Ambient Session Management Tests', () => {
it('should visit each workspace page via direct routes', () => {
// Each workspace section now has its own route (no more ?section= params)

// Sharing page — covers sharing-section.tsx
// Pair Prompting page — covers sharing-section.tsx
cy.visit(`/projects/${workspaceSlug}/permissions`)
cy.get('body', { timeout: 10000 }).should('contain.text', 'Sharing')
cy.get('body', { timeout: 10000 }).should('contain.text', 'Pair Prompting')
cy.wait(500)

// Access Keys page — covers keys page
Expand Down Expand Up @@ -484,7 +484,7 @@ describe('Ambient Session Management Tests', () => {

it('should interact with sharing tab', () => {
cy.visit(`/projects/${workspaceSlug}/permissions`)
cy.get('body', { timeout: 15000 }).should('contain.text', 'Sharing')
cy.get('body', { timeout: 15000 }).should('contain.text', 'Pair Prompting')

// Look for "Grant Permission" button
cy.get('body').then(($body) => {
Expand Down Expand Up @@ -1339,11 +1339,11 @@ describe('Ambient Session Management Tests', () => {

it('should interact with sharing tab Grant Permission dialog', () => {
cy.visit(`/projects/${workspaceSlug}/permissions`)
cy.get('body', { timeout: 15000 }).should('contain.text', 'Sharing')
cy.get('body', { timeout: 15000 }).should('contain.text', 'Pair Prompting')

// Look for "Grant Permission" or "Grant First Permission" button
// Look for "Grant Permission" button
cy.get('body').then(($body) => {
const grantBtn = $body.find('button:contains("Grant Permission"), button:contains("Grant First Permission")')
const grantBtn = $body.find('button:contains("Grant Permission")')
if (grantBtn.length) {
cy.wrap(grantBtn.first()).click({ force: true })
cy.wait(500)
Expand Down Expand Up @@ -1654,7 +1654,7 @@ describe('Ambient Session Management Tests', () => {

it('should click Refresh button on sharing tab', () => {
cy.visit(`/projects/${workspaceSlug}/permissions`)
cy.get('body', { timeout: 15000 }).should('contain.text', 'Sharing')
cy.get('body', { timeout: 15000 }).should('contain.text', 'Pair Prompting')

cy.get('body').then(($body) => {
if ($body.find('button:contains("Refresh")').length) {
Expand Down
Loading