Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8563b37

Browse files
authored
feat: filter templates by organization (#14254)
1 parent4fc0479 commit8563b37

File tree

12 files changed

+169
-45
lines changed

12 files changed

+169
-45
lines changed

‎coderd/database/queries.sql.go

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/queries/templates.sql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ WHERE
2828
LOWER("name")=LOWER(@exact_name)
2929
ELSE true
3030
END
31-
-- Filter by name, matching on substring
32-
AND CASE
33-
WHEN @fuzzy_name ::text!='' THEN
34-
lower(name) ILIKE'%'||lower(@fuzzy_name)||'%'
35-
ELSE true
31+
-- Filter by name, matching on substring
32+
AND CASE
33+
WHEN @fuzzy_name ::text!='' THEN
34+
lower(name) ILIKE'%'||lower(@fuzzy_name)||'%'
35+
ELSE true
3636
END
3737
-- Filter by ids
3838
AND CASE

‎coderd/searchquery/search.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,9 @@ func Templates(ctx context.Context, db database.Store, query string) (database.G
198198

199199
parser:=httpapi.NewQueryParamParser()
200200
filter:= database.GetTemplatesWithFilterParams{
201-
FuzzyName:parser.String(values,"","name"),
202201
Deleted:parser.Boolean(values,false,"deleted"),
203202
ExactName:parser.String(values,"","exact_name"),
203+
FuzzyName:parser.String(values,"","name"),
204204
IDs:parser.UUIDs(values, []uuid.UUID{},"ids"),
205205
Deprecated:parser.NullableBoolean(values, sql.NullBool{},"deprecated"),
206206
}

‎site/src/api/api.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,17 @@ export type GetTemplatesOptions = Readonly<{
304304
readonlydeprecated?:boolean;
305305
}>;
306306

307+
exporttypeGetTemplatesQuery=Readonly<{
308+
readonlyq:string;
309+
}>;
310+
307311
functionnormalizeGetTemplatesOptions(
308-
options:GetTemplatesOptions={},
312+
options:GetTemplatesOptions|GetTemplatesQuery={},
309313
):Record<string,string>{
314+
if("q"inoptions){
315+
returnoptions;
316+
}
317+
310318
constparams:Record<string,string>={};
311319
if(options.deprecated!==undefined){
312320
params["deprecated"]=String(options.deprecated);
@@ -666,6 +674,13 @@ class ApiMethods {
666674
returnresponse.data;
667675
};
668676

677+
getMyOrganizations=async():Promise<TypesGen.Organization[]>=>{
678+
constresponse=awaitthis.axios.get<TypesGen.Organization[]>(
679+
"/api/v2/users/me/organizations",
680+
);
681+
returnresponse.data;
682+
};
683+
669684
/**
670685
*@param organization Can be the organization's ID or name
671686
*/
@@ -687,7 +702,7 @@ class ApiMethods {
687702
};
688703

689704
getTemplates=async(
690-
options?:GetTemplatesOptions,
705+
options?:GetTemplatesOptions|GetTemplatesQuery,
691706
):Promise<TypesGen.Template[]>=>{
692707
constparams=normalizeGetTemplatesOptions(options);
693708
constresponse=awaitthis.axios.get<TypesGen.Template[]>(

‎site/src/api/queries/templates.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
importtype{MutationOptions,QueryClient,QueryOptions}from"react-query";
2-
import{API,typeGetTemplatesOptions}from"api/api";
2+
import{API,typeGetTemplatesQuery,typeGetTemplatesOptions}from"api/api";
33
importtype{
44
CreateTemplateRequest,
55
CreateTemplateVersionRequest,
@@ -38,12 +38,13 @@ export const templateByName = (
3838
};
3939
};
4040

41-
constgetTemplatesQueryKey=(options?:GetTemplatesOptions)=>[
42-
"templates",
43-
options?.deprecated,
44-
];
41+
constgetTemplatesQueryKey=(
42+
options?:GetTemplatesOptions|GetTemplatesQuery,
43+
)=>["templates",options];
4544

46-
exportconsttemplates=(options?:GetTemplatesOptions)=>{
45+
exportconsttemplates=(
46+
options?:GetTemplatesOptions|GetTemplatesQuery,
47+
)=>{
4748
return{
4849
queryKey:getTemplatesQueryKey(options),
4950
queryFn:()=>API.getTemplates(options),

‎site/src/components/Filter/filter.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ type FilterProps = {
137137
filter:ReturnType<typeofuseFilter>;
138138
skeleton:ReactNode;
139139
isLoading:boolean;
140-
learnMoreLink:string;
140+
learnMoreLink?:string;
141141
learnMoreLabel2?:string;
142142
learnMoreLink2?:string;
143143
error?:unknown;
@@ -240,7 +240,7 @@ export const Filter: FC<FilterProps> = ({
240240

241241
interfacePresetMenuProps{
242242
presets:PresetFilter[];
243-
learnMoreLink:string;
243+
learnMoreLink?:string;
244244
learnMoreLabel2?:string;
245245
learnMoreLink2?:string;
246246
onSelect:(query:string)=>void;
@@ -293,19 +293,23 @@ const PresetMenu: FC<PresetMenuProps> = ({
293293
{presetFilter.name}
294294
</MenuItem>
295295
))}
296-
<Dividercss={{borderColor:theme.palette.divider}}/>
297-
<MenuItem
298-
component="a"
299-
href={learnMoreLink}
300-
target="_blank"
301-
css={{fontSize:13,fontWeight:500}}
302-
onClick={()=>{
303-
setIsOpen(false);
304-
}}
305-
>
306-
<OpenInNewOutlinedcss={{fontSize:"14px !important"}}/>
307-
View advanced filtering
308-
</MenuItem>
296+
{learnMoreLink&&(
297+
<>
298+
<Dividercss={{borderColor:theme.palette.divider}}/>
299+
<MenuItem
300+
component="a"
301+
href={learnMoreLink}
302+
target="_blank"
303+
css={{fontSize:13,fontWeight:500}}
304+
onClick={()=>{
305+
setIsOpen(false);
306+
}}
307+
>
308+
<OpenInNewOutlinedcss={{fontSize:"14px !important"}}/>
309+
View advanced filtering
310+
</MenuItem>
311+
</>
312+
)}
309313
{learnMoreLink2&&learnMoreLabel2&&(
310314
<MenuItem
311315
component="a"
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
importtype{FC}from"react";
2+
import{API}from"api/api";
3+
importtype{Organization}from"api/typesGenerated";
4+
import{
5+
Filter,
6+
MenuSkeleton,
7+
SearchFieldSkeleton,
8+
typeuseFilter,
9+
}from"components/Filter/filter";
10+
import{useFilterMenu}from"components/Filter/menu";
11+
import{
12+
SelectFilter,
13+
typeSelectFilterOption,
14+
}from"components/Filter/SelectFilter";
15+
import{UserAvatar}from"components/UserAvatar/UserAvatar";
16+
17+
interfaceTemplatesFilterProps{
18+
filter:ReturnType<typeofuseFilter>;
19+
}
20+
21+
exportconstTemplatesFilter:FC<TemplatesFilterProps>=({ filter})=>{
22+
constorganizationMenu=useFilterMenu({
23+
onChange:(option)=>
24+
filter.update({ ...filter.values,organization:option?.value}),
25+
value:filter.values.organization,
26+
id:"organization",
27+
getSelectedOption:async()=>{
28+
if(!filter.values.organization){
29+
returnnull;
30+
}
31+
32+
constorg=awaitAPI.getOrganization(filter.values.organization);
33+
returnorgOption(org);
34+
},
35+
getOptions:async()=>{
36+
constorgs=awaitAPI.getMyOrganizations();
37+
returnorgs.map(orgOption);
38+
},
39+
});
40+
41+
return(
42+
<Filter
43+
presets={[
44+
{query:"",name:"All templates"},
45+
{query:"deprecated:true",name:"Deprecated templates"},
46+
]}
47+
// TODO: Add docs for this
48+
// learnMoreLink={docs("/templates#template-filtering")}
49+
isLoading={false}
50+
filter={filter}
51+
options={
52+
<>
53+
<SelectFilter
54+
placeholder="All organizations"
55+
label="Select an organization"
56+
options={organizationMenu.searchOptions}
57+
selectedOption={organizationMenu.selectedOption??undefined}
58+
onSelect={organizationMenu.selectOption}
59+
/>
60+
</>
61+
}
62+
skeleton={
63+
<>
64+
<SearchFieldSkeleton/>
65+
<MenuSkeleton/>
66+
</>
67+
}
68+
/>
69+
);
70+
};
71+
72+
constorgOption=(org:Organization):SelectFilterOption=>({
73+
label:org.display_name||org.name,
74+
value:org.name,
75+
startIcon:(
76+
<UserAvatar
77+
key={org.id}
78+
size="xs"
79+
username={org.display_name}
80+
avatarURL={org.icon}
81+
/>
82+
),
83+
});

‎site/src/pages/TemplatesPage/TemplatesPage.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
importtype{FC}from"react";
22
import{Helmet}from"react-helmet-async";
33
import{useQuery}from"react-query";
4+
import{useSearchParams}from"react-router-dom";
45
import{templateExamples,templates}from"api/queries/templates";
6+
import{useFilter}from"components/Filter/filter";
57
import{useAuthenticated}from"contexts/auth/RequireAuth";
68
import{useDashboard}from"modules/dashboard/useDashboard";
79
import{pageTitle}from"utils/page";
@@ -11,7 +13,14 @@ export const TemplatesPage: FC = () => {
1113
const{ permissions}=useAuthenticated();
1214
const{ showOrganizations}=useDashboard();
1315

14-
consttemplatesQuery=useQuery(templates());
16+
constsearchParamsResult=useSearchParams();
17+
constfilter=useFilter({
18+
fallbackFilter:"deprecated:false",
19+
searchParamsResult,
20+
onUpdate:()=>{},// reset pagination
21+
});
22+
23+
consttemplatesQuery=useQuery(templates({q:filter.query}));
1524
constexamplesQuery=useQuery({
1625
...templateExamples(),
1726
enabled:permissions.createTemplates,
@@ -25,6 +34,7 @@ export const TemplatesPage: FC = () => {
2534
</Helmet>
2635
<TemplatesPageView
2736
error={error}
37+
filter={filter}
2838
showOrganizations={showOrganizations}
2939
canCreateTemplates={permissions.createTemplates}
3040
examples={examplesQuery.data}

‎site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{getDefaultFilterProps}from"components/Filter/storyHelpers";
23
import{chromaticWithTablet}from"testHelpers/chromatic";
34
import{
45
mockApiError,
@@ -14,6 +15,13 @@ const meta: Meta<typeof TemplatesPageView> = {
1415
decorators:[withDashboardProvider],
1516
parameters:{chromatic:chromaticWithTablet},
1617
component:TemplatesPageView,
18+
args:{
19+
...getDefaultFilterProps({
20+
query:"deprecated:false",
21+
menus:{},
22+
values:{},
23+
}),
24+
},
1725
};
1826

1927
exportdefaultmeta;

‎site/src/pages/TemplatesPage/TemplatesPageView.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ExternalAvatar } from "components/Avatar/Avatar";
1717
import{AvatarData}from"components/AvatarData/AvatarData";
1818
import{AvatarDataSkeleton}from"components/AvatarData/AvatarDataSkeleton";
1919
import{DeprecatedBadge}from"components/Badges/Badges";
20+
importtype{useFilter}from"components/Filter/filter";
2021
import{
2122
HelpTooltip,
2223
HelpTooltipContent,
@@ -46,6 +47,7 @@ import {
4647
formatTemplateActiveDevelopers,
4748
}from"utils/templates";
4849
import{EmptyTemplates}from"./EmptyTemplates";
50+
import{TemplatesFilter}from"./TemplatesFilter";
4951

5052
exportconstLanguage={
5153
developerCount:(activeCount:number):string=>{
@@ -173,6 +175,7 @@ const TemplateRow: FC<TemplateRowProps> = ({ showOrganizations, template }) => {
173175

174176
exportinterfaceTemplatesPageViewProps{
175177
error?:unknown;
178+
filter:ReturnType<typeofuseFilter>;
176179
showOrganizations:boolean;
177180
canCreateTemplates:boolean;
178181
examples:TemplateExample[]|undefined;
@@ -181,6 +184,7 @@ export interface TemplatesPageViewProps {
181184

182185
exportconstTemplatesPageView:FC<TemplatesPageViewProps>=({
183186
error,
187+
filter,
184188
showOrganizations,
185189
canCreateTemplates,
186190
examples,
@@ -213,13 +217,13 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = ({
213217
<TemplateHelpTooltip/>
214218
</Stack>
215219
</PageHeaderTitle>
216-
{templates&&templates.length>0&&(
217-
<PageHeaderSubtitle>
218-
Select a template to create a workspace.
219-
</PageHeaderSubtitle>
220-
)}
220+
<PageHeaderSubtitle>
221+
Select a template to create a workspace.
222+
</PageHeaderSubtitle>
221223
</PageHeader>
222224

225+
<TemplatesFilterfilter={filter}/>
226+
223227
{error ?(
224228
<ErrorAlerterror={error}/>
225229
) :(

‎site/src/pages/WorkspacesPage/WorkspacesPageView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import StopOutlined from "@mui/icons-material/StopOutlined";
66
importLoadingButtonfrom"@mui/lab/LoadingButton";
77
importButtonfrom"@mui/material/Button";
88
importDividerfrom"@mui/material/Divider";
9-
importtype{ComponentProps}from"react";
9+
importtype{ComponentProps,FC}from"react";
1010
importtype{UseQueryResult}from"react-query";
1111
import{hasError,isApiValidationError}from"api/errors";
1212
importtype{Template,Workspace}from"api/typesGenerated";
@@ -65,7 +65,7 @@ export interface WorkspacesPageViewProps {
6565
canChangeVersions:boolean;
6666
}
6767

68-
exportconstWorkspacesPageView=({
68+
exportconstWorkspacesPageView:FC<WorkspacesPageViewProps>=({
6969
workspaces,
7070
error,
7171
limit,
@@ -86,7 +86,7 @@ export const WorkspacesPageView = ({
8686
templatesFetchStatus,
8787
canCreateTemplate,
8888
canChangeVersions,
89-
}:WorkspacesPageViewProps)=>{
89+
})=>{
9090
// Let's say the user has 5 workspaces, but tried to hit page 100, which does
9191
// not exist. In this case, the page is not valid and we want to show a better
9292
// error message.

‎site/src/pages/WorkspacesPage/filter/menus.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ export const useTemplateFilterMenu = ({
4343
template.display_name.toLowerCase().includes(query.toLowerCase()),
4444
);
4545
returnfilteredTemplates.map((template)=>({
46-
label:
47-
template.display_name!=="" ?template.display_name :template.name,
46+
label:template.display_name||template.name,
4847
value:template.name,
4948
startIcon:<TemplateAvatarsize="xs"template={template}/>,
5049
}));

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp