Skip to content

Commit

Permalink
feat: add org search (#1620)
Browse files Browse the repository at this point in the history
* feat: add org search

* fix: type

* add default search to org

* add redirect for org analyze

---------

Co-authored-by: Jagger <634750802@qq.com>
  • Loading branch information
shczhen and 634750802 authored Oct 11, 2023
1 parent 88f21b1 commit 6b4d41b
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 116 deletions.
7 changes: 7 additions & 0 deletions web/src/api/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
SearchRepoInfo,
UserInfo,
UserType,
SearchOrgInfo,
} from '@ossinsight/api';
import {
AxiosAdapter,
Expand Down Expand Up @@ -78,6 +79,12 @@ export async function searchUser (
.then(({ data }) => data);
}

export async function searchOrg (keyword: string): Promise<SearchOrgInfo[]> {
return await client
.get('/gh/organizations/search', { params: { keyword } })
.then(({ data }) => data);
}

export async function getCollections (): Promise<RemoteData<{}, Collection>> {
return await client.get('/collections');
}
Expand Down
270 changes: 177 additions & 93 deletions web/src/components/GeneralSearch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import {
Tabs,
TextField,
Typography,
ListSubheader,
} from '@mui/material';
import { SearchRepoInfo, UserInfo } from '@ossinsight/api';
import { SearchRepoInfo, UserInfo, SearchOrgInfo } from '@ossinsight/api';
import React, {
FC, ForwardedRef,
forwardRef,
Expand All @@ -41,7 +42,7 @@ import KeyboardUpIcon from '@mui/icons-material/KeyboardArrowUp';
import KeyboardDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardReturnIcon from '@mui/icons-material/KeyboardReturn';
import { AutocompleteHighlightChangeReason } from '@mui/base/AutocompleteUnstyled/useAutocomplete';
import { notNullish } from '@site/src/utils/value';
import { notNullish, isFalsy } from '@site/src/utils/value';

export interface GeneralSearchProps {
contrast?: boolean;
Expand All @@ -50,16 +51,16 @@ export interface GeneralSearchProps {
global?: boolean;
}

type Option = SearchRepoInfo | UserInfo;
type Option = SearchRepoInfo | UserInfo | SearchOrgInfo;

const isOptionEqual = (a: Option, b: Option) => {
return a.id === b.id;
};

const getOptionLabel = (option: Option) => (option as SearchRepoInfo).fullName || (option as UserInfo).login;
const getOptionLabel = (option: Option) => (option as SearchRepoInfo).fullName || (option as UserInfo | SearchOrgInfo).login || (option as any).label;

const useTabs = () => {
const [type, setType] = useState<SearchType>('user');
const [type, setType] = useState<SearchType>('all');

const handleTypeChange = useEventCallback((_: any, type: SearchType) => {
setType(type);
Expand All @@ -68,14 +69,22 @@ const useTabs = () => {
const tabs = useMemo(() => {
return (
<Tabs value={type} onChange={handleTypeChange}>
<Tab label="All" value="all" />
<Tab label="Developer" value="user" />
<Tab label="Repo" value="repo" />
<Tab label="Org" value="org" />
</Tabs>
);
}, [type]);

const next = useEventCallback(() => {
setType(type => type === 'user' ? 'repo' : 'user');
const types: SearchType[] = ['all', 'user', 'repo', 'org'];
const idx = types.findIndex(t => t === type);
if (idx >= types.length - 1) {
setType('all');
return;
}
setType(types[idx + 1]);
});

return [type, tabs, next] as const;
Expand Down Expand Up @@ -163,6 +172,23 @@ export const renderRepo = (props: ListItemProps, option: Option, highlight: bool
);
};

export const renderSubListItem = (props: ListItemProps, option: any) => {
return (
<>
<ListSubheader
disableSticky
sx={{
'.MuiListSubheader-root': {
lineHeight: 1.5,
},
}}
>
{option?.label}
</ListSubheader>
</>
);
};

const GeneralSearch: FC<GeneralSearchProps> = ({ contrast, align = 'left', size, global = false }: GeneralSearchProps) => {
const [keyword, setKeyword] = useState('');
const [type, tabs, next] = useTabs();
Expand All @@ -183,6 +209,18 @@ const GeneralSearch: FC<GeneralSearchProps> = ({ contrast, align = 'left', size,
case 'user':
history.push(`/analyze/${(option as UserInfo).login}`);
break;
case 'org':
typeof window !== 'undefined' && (window.location.href = `https://next.ossinsight.io/analyze/${(option as UserInfo).login}`);
break;
case 'all':
if ((option as any).type === 'Repo') {
history.push(`/analyze/${(option as SearchRepoInfo).fullName}`);
} else if ((option as any).type === 'User') {
history.push(`/analyze/${(option as UserInfo).login}`);
} else if ((option as any).type === 'Org') {
typeof window !== 'undefined' && (window.location.href = `https://next.ossinsight.io/analyze/${(option as UserInfo).login}`);
}
break;
}
}, [type]);

Expand All @@ -201,11 +239,15 @@ const GeneralSearch: FC<GeneralSearchProps> = ({ contrast, align = 'left', size,

const placeholder = useMemo(() => {
if (!open) {
return 'Search a developer or repo';
return 'Search a developer/repo/org';
} else if (type === 'user') {
return 'Enter a GitHub ID';
} else {
} else if (type === 'repo') {
return 'Enter a GitHub Repo Name';
} else if (type === 'org') {
return 'Enter a GitHub Org Name';
} else {
return 'Enter a GitHub ID/Repo/Org Name';
}
}, [open, type]);

Expand Down Expand Up @@ -250,6 +292,9 @@ const GeneralSearch: FC<GeneralSearchProps> = ({ contrast, align = 'left', size,
onClose={handleClose}
loading={loading}
options={list ?? []}
groupBy={(option: any) => {
return option?.type;
}}
isOptionEqualToValue={isOptionEqual}
getOptionLabel={getOptionLabel}
value={option}
Expand All @@ -258,103 +303,142 @@ const GeneralSearch: FC<GeneralSearchProps> = ({ contrast, align = 'left', size,
onInputChange={handleInputChange}
onHighlightChange={handleHighlightChange}
forcePopupIcon={false}
sx={useMemo(() => ({
maxWidth: size === 'large' ? 540 : 300,
flex: 1,
}), [size])}
renderOption={useCallback((props, option) => (
type === 'repo'
? renderRepo(props, option, highlight === option)
: renderUser(props, option, highlight === option)
), [type, highlight])}
renderInput={useCallback(({ InputProps, ...params }) => (
<TextField
{...params}
variant='outlined'
placeholder={placeholder}
sx={theme => ({
backgroundColor: contrast ? '#E9EAEE' : '#3c3c3c',
borderRadius: 2,
color: contrast ? theme.palette.getContrastText('#E9EAEE') : undefined,
'.MuiAutocomplete-input': {
textAlign: align,
},
'.MuiOutlinedInput-notchedOutline': {
border: 'none',
},
fontSize: size === 'large' ? 24 : undefined,
py: size === 'large' ? '4px !important' : undefined,
})}
inputRef={inputRef}
InputProps={{
...InputProps,
onKeyDown: handleKeyDown,
sx: theme => ({
sx={useMemo(
() => ({
maxWidth: size === 'large' ? 540 : 300,
flex: 1,
}),
[size],
)}
renderOption={useCallback(
(props, option) => {
if (!isFalsy(option?.login)) {
return renderUser(props, option, highlight === option);
}
if (!isFalsy(option?.fullName)) {
return renderRepo(props, option, highlight === option);
}
},
[type, highlight],
)}
renderInput={useCallback(
({ InputProps, ...params }) => (
<TextField
{...params}
variant='outlined'
placeholder={placeholder}
sx={(theme) => ({
backgroundColor: contrast ? '#E9EAEE' : '#3c3c3c',
color: contrast ? theme.palette.getContrastText('#E9EAEE') : undefined,
}),
startAdornment: (
<InputAdornment position="start">
<SearchIcon sx={theme => ({ color: contrast ? theme.palette.getContrastText('#E9EAEE') : undefined })} />
</InputAdornment>
),
endAdornment: loading
? (
<InputAdornment position="end">
<CircularProgress size='16px' sx={{ mr: 1 }} color={contrast ? 'info' : 'primary'} />
</InputAdornment>
)
: (global && !open && !option) ? <TipIcon icon='/' reverse display={[false, true]} /> : undefined,
}}
/>
), [open, global, contrast, align, size, loading, option])}
noOptionsText={(
borderRadius: 2,
color: contrast
? theme.palette.getContrastText('#E9EAEE')
: undefined,
'.MuiAutocomplete-input': {
textAlign: align,
},
'.MuiOutlinedInput-notchedOutline': {
border: 'none',
},
fontSize: size === 'large' ? 24 : undefined,
py: size === 'large' ? '4px !important' : undefined,
})}
inputRef={inputRef}
InputProps={{
...InputProps,
onKeyDown: handleKeyDown,
sx: (theme) => ({
backgroundColor: contrast ? '#E9EAEE' : '#3c3c3c',
color: contrast
? theme.palette.getContrastText('#E9EAEE')
: undefined,
}),
startAdornment: (
<InputAdornment position='start'>
<SearchIcon
sx={(theme) => ({
color: contrast
? theme.palette.getContrastText('#E9EAEE')
: undefined,
})}
/>
</InputAdornment>
),
endAdornment: loading
? (
<InputAdornment position='end'>
<CircularProgress
size='16px'
sx={{ mr: 1 }}
color={contrast ? 'info' : 'primary'}
/>
</InputAdornment>
)
: global && !open && !option
? (
<TipIcon icon='/' reverse display={[false, true]} />
)
: undefined,
}}
/>
),
[open, global, contrast, align, size, loading, option],
)}
noOptionsText={
<PopperContainer>
{tabs}
<Box p={2}>
<Typography variant="body2">
No {type}
</Typography>
{type !== 'all' && (<Typography variant='body2'>No {type}</Typography>)}
</Box>
</PopperContainer>
)}
loadingText={(
}
loadingText={
<PopperContainer>
{tabs}
<Box p={2}>
<Skeleton animation="wave" />
<Skeleton animation="wave" />
<Skeleton animation="wave" />
<Skeleton animation='wave' />
<Skeleton animation='wave' />
<Skeleton animation='wave' />
</Box>
</PopperContainer>
}
ListboxComponent={useCallback(
forwardRef(function SearchList (
{ children, ...props }: React.HTMLAttributes<HTMLElement>,
ref: ForwardedRef<HTMLElement>,
) {
return (
<PopperContainer ref={ref} {...props}>
{tabs}
<List dense disablePadding>
{children}
</List>
<Box
height={32}
p={0.5}
bgcolor='#121212'
display={['none', 'block']}
>
<Stack direction='row'>
<TipGroup text='To Navigate'>
<Stack direction='row'>
<TipIcon textContent icon='TAB' />
<TipIcon icon={<KeyboardUpIcon fontSize='inherit' />} />
<TipIcon icon={<KeyboardDownIcon fontSize='inherit' />} />
</Stack>
</TipGroup>
<TipGroup text='To Cancel'>
<TipIcon textContent icon='ESC' />
</TipGroup>
<TipGroup text='To Enter'>
<TipIcon icon={<KeyboardReturnIcon fontSize='inherit' />} />
</TipGroup>
</Stack>
</Box>
</PopperContainer>
);
}),
[tabs],
)}
ListboxComponent={useCallback(forwardRef(function SearchList ({ children, ...props }: React.HTMLAttributes<HTMLElement>, ref: ForwardedRef<HTMLElement>) {
return (
<PopperContainer ref={ref} {...props}>
{tabs}
<List dense disablePadding>
{children}
</List>
<Box height={32} p={0.5} bgcolor='#121212' display={['none', 'block']}>
<Stack direction='row'>
<TipGroup text='To Navigate'>
<Stack direction='row'>
<TipIcon textContent icon='TAB' />
<TipIcon icon={<KeyboardUpIcon fontSize='inherit'/>} />
<TipIcon icon={<KeyboardDownIcon fontSize='inherit'/>} />
</Stack>
</TipGroup>
<TipGroup text='To Cancel'>
<TipIcon textContent icon='ESC' />
</TipGroup>
<TipGroup text='To Enter'>
<TipIcon icon={<KeyboardReturnIcon fontSize='inherit'/>} />
</TipGroup>
</Stack>
</Box>
</PopperContainer>
);
}), [tabs])}
PopperComponent={CustomPopper}
/>
);
Expand Down
Loading

0 comments on commit 6b4d41b

Please sign in to comment.