Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 2451aab

Browse files
authored
saved searches & prompt library fixes (#63930)
- Add spacing at bottom of page - Fix viewerCanAdminister check to return `false` not an error when the user is not an administrator of a saved search or prompt - Improve display of prompt description ## Test plan In dotcom mode, try loading a prompt or saved search in incognito mode.
1 parent 3c8b8e9 commit 2451aab

File tree

12 files changed

+120
-98
lines changed

12 files changed

+120
-98
lines changed

client/web/src/prompts/DetailPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ const Detail: FunctionComponent<TelemetryV2Props & { prompt: PromptFields }> = (
8585
return (
8686
<>
8787
<Text>
88-
<LibraryItemVisibilityBadge item={prompt} className="mr-1" />
89-
<LibraryItemStatusBadge item={prompt} className="mr-1" />
9088
{prompt.description}
9189
{prompt.description ? ' — ' : ''}
90+
<LibraryItemVisibilityBadge item={prompt} />
91+
<LibraryItemStatusBadge item={prompt} className="ml-1" />{' '}
9292
<small>
9393
Last updated <Timestamp date={prompt.updatedAt} noAbout={true} />
9494
{prompt.updatedBy && (

client/web/src/prompts/ListPage.tsx

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
usePageSwitcherPagination,
2424
type PaginationKeys,
2525
} from '../components/FilteredConnection/hooks/usePageSwitcherPagination'
26-
import { ConnectionContainer, ConnectionForm } from '../components/FilteredConnection/ui'
26+
import { ConnectionForm } from '../components/FilteredConnection/ui'
2727
import { PromptsOrderBy, type PromptFields, type PromptsResult, type PromptsVariables } from '../graphql-operations'
2828
import { LibraryItemStatusBadge, LibraryItemVisibilityBadge } from '../library/itemBadges'
2929
import { useAffiliatedNamespaces } from '../namespaces/useAffiliatedNamespaces'
@@ -167,40 +167,36 @@ export const ListPage: FunctionComponent<TelemetryV2Props> = ({ telemetryRecorde
167167
return (
168168
<>
169169
<Container data-testid="prompts-list-page">
170-
<ConnectionContainer>
171-
<ConnectionForm
172-
hideSearch={false}
173-
showSearchFirst={true}
174-
inputClassName="mw-30"
175-
inputPlaceholder="Find a prompt..."
176-
inputAriaLabel=""
177-
inputValue={connectionState.query}
178-
onInputChange={event => {
179-
setConnectionState(prev => ({ ...prev, query: event.target.value }))
180-
}}
181-
autoFocus={false}
182-
filters={filters}
183-
onFilterSelect={(filter, value) =>
184-
setConnectionState(prev => ({ ...prev, [filter.id]: value }))
185-
}
186-
filterValues={connectionState}
187-
compact={false}
188-
formClassName="flex-gap-4 mb-4"
189-
/>
190-
{loading ? (
191-
<LoadingSpinner />
192-
) : error ? (
193-
<ErrorAlert error={error} className="mb-3" />
194-
) : !connection?.nodes || connection.nodes.length === 0 ? (
195-
<Text className="text-center text-muted mb-0">No prompts found.</Text>
196-
) : (
197-
<div className="list-group list-group-flush">
198-
{connection.nodes.map(prompt => (
199-
<PromptNode key={prompt.id} prompt={prompt} telemetryRecorder={telemetryRecorder} />
200-
))}
201-
</div>
202-
)}
203-
</ConnectionContainer>
170+
<ConnectionForm
171+
hideSearch={false}
172+
showSearchFirst={true}
173+
inputClassName="mw-30"
174+
inputPlaceholder="Find a prompt..."
175+
inputAriaLabel=""
176+
inputValue={connectionState.query}
177+
onInputChange={event => {
178+
setConnectionState(prev => ({ ...prev, query: event.target.value }))
179+
}}
180+
autoFocus={false}
181+
filters={filters}
182+
onFilterSelect={(filter, value) => setConnectionState(prev => ({ ...prev, [filter.id]: value }))}
183+
filterValues={connectionState}
184+
compact={false}
185+
formClassName="flex-gap-4 mb-4"
186+
/>
187+
{loading ? (
188+
<LoadingSpinner />
189+
) : error ? (
190+
<ErrorAlert error={error} className="mb-3" />
191+
) : !connection?.nodes || connection.nodes.length === 0 ? (
192+
<Text className="text-center text-muted mb-0">No prompts found.</Text>
193+
) : (
194+
<div className="list-group list-group-flush">
195+
{connection.nodes.map(prompt => (
196+
<PromptNode key={prompt.id} prompt={prompt} telemetryRecorder={telemetryRecorder} />
197+
))}
198+
</div>
199+
)}
204200
</Container>
205201
<PageSwitcher {...paginationProps} className="mt-4" totalCount={connection?.totalCount ?? null} />
206202
</>

client/web/src/prompts/Page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const PromptPage: FunctionComponent<{
4444
</PageHeader.Heading>
4545
</PageHeader>
4646
{children}
47+
<div className="pb-4" />
4748
</Page>
4849
)
4950
}
Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,41 @@
1-
import AnimationPlayIcon from 'mdi-react/AnimationPlayIcon'
1+
import type { FunctionComponent } from 'react'
22

3-
export const PromptIcon = AnimationPlayIcon
3+
/**
4+
* The icon for prompts in the Prompt Library, which is the Lucide `book-text` icon.
5+
*/
6+
export const PromptIcon: FunctionComponent<{ className?: string }> = ({ className }) => (
7+
<svg
8+
xmlns="http://www.w3.org/2000/svg"
9+
width="24"
10+
height="24"
11+
viewBox="0 0 24 24"
12+
fill="none"
13+
stroke="currentColor"
14+
strokeWidth="2"
15+
strokeLinecap="round"
16+
strokeLinejoin="round"
17+
className={className}
18+
>
19+
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" />
20+
<path d="M8 11h8" />
21+
<path d="M8 7h6" />
22+
</svg>
23+
)
24+
25+
// Lucide icon set license
26+
//
27+
// ISC License
28+
//
29+
// Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT).
30+
// All other copyright (c) for Lucide are held by Lucide Contributors 2022.
31+
//
32+
// Permission to use, copy, modify, and/or distribute this software for any purpose with or without
33+
// fee is hereby granted, provided that the above copyright notice and this permission notice appear
34+
// in all copies.
35+
//
36+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
37+
// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
38+
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
39+
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
40+
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
41+
// OF THIS SOFTWARE.

client/web/src/savedSearches/ListPage.tsx

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
usePageSwitcherPagination,
2626
type PaginationKeys,
2727
} from '../components/FilteredConnection/hooks/usePageSwitcherPagination'
28-
import { ConnectionContainer, ConnectionForm } from '../components/FilteredConnection/ui'
28+
import { ConnectionForm } from '../components/FilteredConnection/ui'
2929
import {
3030
SavedSearchesOrderBy,
3131
type SavedSearchFields,
@@ -199,45 +199,41 @@ export const ListPage: FunctionComponent<TelemetryV2Props> = ({ telemetryRecorde
199199
return (
200200
<>
201201
<Container data-testid="saved-searches-list-page">
202-
<ConnectionContainer>
203-
<ConnectionForm
204-
hideSearch={false}
205-
showSearchFirst={true}
206-
inputClassName="mw-30"
207-
inputPlaceholder="Find a saved search..."
208-
inputAriaLabel=""
209-
inputValue={connectionState.query}
210-
onInputChange={event => {
211-
setConnectionState(prev => ({ ...prev, query: event.target.value }))
212-
}}
213-
autoFocus={false}
214-
filters={filters}
215-
onFilterSelect={(filter, value) =>
216-
setConnectionState(prev => ({ ...prev, [filter.id]: value }))
217-
}
218-
filterValues={connectionState}
219-
compact={false}
220-
formClassName="flex-gap-4 mb-4"
221-
/>
222-
{loading ? (
223-
<LoadingSpinner />
224-
) : error ? (
225-
<ErrorAlert error={error} className="mb-3" />
226-
) : !connection?.nodes || connection.nodes.length === 0 ? (
227-
<Text className="text-center text-muted mb-0">No saved searches found.</Text>
228-
) : (
229-
<div className="list-group list-group-flush">
230-
{connection.nodes.map(savedSearch => (
231-
<SavedSearchNode
232-
key={savedSearch.id}
233-
patternType={searchPatternType}
234-
savedSearch={savedSearch}
235-
telemetryRecorder={telemetryRecorder}
236-
/>
237-
))}
238-
</div>
239-
)}
240-
</ConnectionContainer>
202+
<ConnectionForm
203+
hideSearch={false}
204+
showSearchFirst={true}
205+
inputClassName="mw-30"
206+
inputPlaceholder="Find a saved search..."
207+
inputAriaLabel=""
208+
inputValue={connectionState.query}
209+
onInputChange={event => {
210+
setConnectionState(prev => ({ ...prev, query: event.target.value }))
211+
}}
212+
autoFocus={false}
213+
filters={filters}
214+
onFilterSelect={(filter, value) => setConnectionState(prev => ({ ...prev, [filter.id]: value }))}
215+
filterValues={connectionState}
216+
compact={false}
217+
formClassName="flex-gap-4 mb-4"
218+
/>
219+
{loading ? (
220+
<LoadingSpinner />
221+
) : error ? (
222+
<ErrorAlert error={error} className="mb-3" />
223+
) : !connection?.nodes || connection.nodes.length === 0 ? (
224+
<Text className="text-center text-muted mb-0">No saved searches found.</Text>
225+
) : (
226+
<div className="list-group list-group-flush">
227+
{connection.nodes.map(savedSearch => (
228+
<SavedSearchNode
229+
key={savedSearch.id}
230+
patternType={searchPatternType}
231+
savedSearch={savedSearch}
232+
telemetryRecorder={telemetryRecorder}
233+
/>
234+
))}
235+
</div>
236+
)}
241237
</Container>
242238
<PageSwitcher {...paginationProps} className="mt-4" totalCount={connection?.totalCount ?? null} />
243239
</>

client/web/src/savedSearches/Page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { PageHeader } from '@sourcegraph/wildcard'
77

88
import { Page } from '../components/Page'
99
import { PageTitle } from '../components/PageTitle'
10-
import { type SavedSearchFields } from '../graphql-operations'
10+
import type { SavedSearchFields } from '../graphql-operations'
1111
import { PageRoutes } from '../routes.constants'
1212

1313
import { urlToSavedSearchesList } from './ListPage'
@@ -47,6 +47,7 @@ export const SavedSearchPage: FunctionComponent<{
4747
</PageHeader.Heading>
4848
</PageHeader>
4949
{children}
50+
<div className="pb-4" />
5051
</Page>
5152
)
5253
}

cmd/frontend/graphqlbackend/prompts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ type PromptResolver interface {
6262
UpdatedBy(context.Context) (*UserResolver, error)
6363
UpdatedAt() gqlutil.DateTime
6464
URL() string
65-
ViewerCanAdminister(context.Context) (bool, error)
65+
ViewerCanAdminister(context.Context) bool
6666
}
6767

6868
type PromptDefinitionResolver struct {

cmd/frontend/graphqlbackend/saved_searches.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type SavedSearchResolver interface {
6060
UpdatedBy(context.Context) (*UserResolver, error)
6161
UpdatedAt() gqlutil.DateTime
6262
URL() string
63-
ViewerCanAdminister(context.Context) (bool, error)
63+
ViewerCanAdminister(context.Context) bool
6464
}
6565

6666
type SavedSearchesArgs struct {

cmd/frontend/internal/prompts/resolvers/resolvers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,12 @@ func (r *promptResolver) URL() string {
165165
return "/prompts/" + string(r.ID())
166166
}
167167

168-
func (r *promptResolver) ViewerCanAdminister(ctx context.Context) (bool, error) {
168+
func (r *promptResolver) ViewerCanAdminister(ctx context.Context) bool {
169169
// 🚨 SECURITY: If the visibility is public, then the user can see it, but they can only
170170
// administer it if they are authorized for the namespace (as an org member or their own user
171171
// account).
172172
err := graphqlbackend.CheckAuthorizedForNamespaceByIDs(ctx, r.db, r.s.Owner)
173-
return err == nil, err
173+
return err == nil
174174
}
175175

176176
func (r *Resolver) toPromptResolver(entry types.Prompt) *promptResolver {

cmd/frontend/internal/prompts/resolvers/resolvers_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -566,12 +566,7 @@ func TestPromptPermissions(t *testing.T) {
566566
t.Fatalf("got couldView %v (error %v), want %v", couldView, err, tt.viewerCanView)
567567
}
568568
if result != nil {
569-
gotCanAdminister, err := result.ViewerCanAdminister(ctx)
570-
if tt.opErrIs == nil {
571-
require.NoError(t, err)
572-
} else {
573-
require.ErrorAs(t, err, &tt.opErrIs)
574-
}
569+
gotCanAdminister := result.ViewerCanAdminister(ctx)
575570
if gotCanAdminister != tt.viewerCanAdminister {
576571
t.Errorf("got %v, want %v", gotCanAdminister, tt.viewerCanAdminister)
577572
}

0 commit comments

Comments
 (0)