Skip to content

Commit 8a96d5d

Browse files
authored
Merge pull request #14 from StackExchange/1.4-refinement
1.4 refinement
2 parents e7b42aa + e62af74 commit 8a96d5d

File tree

10 files changed

+155
-85
lines changed

10 files changed

+155
-85
lines changed

plugins/stack-overflow-teams-backend/src/api/createStackOverflowApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const createStackOverflowApi = (baseUrl: string) => {
4040

4141
const headers: Record<string, string> = {
4242
'Content-Type': 'application/json',
43+
'User-Agent': 'SOBackstage-Plugin',
4344
Authorization: `Bearer ${authToken}`,
4445
};
4546

plugins/stack-overflow-teams-backend/src/router.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ export async function createRouter({
265265
try {
266266
const baseUrl = stackOverflowConfig.baseUrl;
267267
const teamsAPIUrl = 'https://api.stackoverflowteams.com';
268-
const teamsBaseUrl = `https://stackoverflowteams.com/c/${stackOverflowConfig.teamName}`; // Fixed URL to match your previous code
268+
const teamsBaseUrl = `https://stackoverflowteams.com/c/${stackOverflowConfig.teamName}`;
269269

270270
if (baseUrl === teamsAPIUrl) {
271271
return res.json({ SOInstance: teamsBaseUrl, teamName: stackOverflowConfig.teamName });
@@ -296,7 +296,6 @@ export async function createRouter({
296296
}
297297
});
298298

299-
// Updated questions route with filtering support
300299
router.get('/questions', async (req: Request, res: Response) => {
301300
try {
302301
const authToken = getValidAuthToken(req, res);

plugins/stack-overflow-teams/src/components/StackOverflow/StackOverflowPostQuestionModal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ export const StackOverflowPostQuestionModal = () => {
743743
</Alert>
744744
)}
745745

746-
<Box mt={3} sx={{ borderTop: 1, borderColor: 'divider', pt: 2, display: 'flex', gap: 2 }}>
746+
<Box my={3} sx={{ borderTop: 1, borderColor: 'divider', pt: 2, display: 'flex', gap: 2 }}>
747747
<Button
748748
variant="contained"
749749
className={classes.button}
@@ -793,7 +793,7 @@ export const StackOverflowPostQuestionModal = () => {
793793
<Grid item xs={12} md={8}>
794794
{renderQuestionForm()}
795795
</Grid>
796-
<Grid item xs={12} md={4}>
796+
<Grid item xs={12} md={4} >
797797
{renderRightPanel()}
798798
</Grid>
799799
</Grid>
@@ -808,7 +808,7 @@ export const StackOverflowPostQuestionModal = () => {
808808
top: '50%',
809809
left: '50%',
810810
transform: 'translate(-50%, -50%)',
811-
width: { xs: '95vw', sm: '90vw', md: '80vw', lg: '70vw' },
811+
width: { xs: '95vw', sm: '90vw', md: '80vw', lg: '80vw', xl: '70vw' },
812812
maxHeight: '90vh',
813813
bgcolor: 'background.paper',
814814
boxShadow: 24,

plugins/stack-overflow-teams/src/components/StackOverflow/StackOverflowPosts.tsx

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ import {
1212
Box,
1313
} from '@material-ui/core';
1414
import Skeleton from '@mui/material/Skeleton';
15-
import { Link, ResponseErrorPanel } from '@backstage/core-components';
15+
import { ResponseErrorPanel } from '@backstage/core-components';
1616
import { useStackOverflowSearch } from './hooks';
1717
import { useStackOverflowData } from './hooks';
1818
import { StackOverflowSearchResultListItem } from './StackOverflowSearchResultListItem';
1919
import SearchIcon from '@material-ui/icons/Search';
2020
import { StackOverflowIcon } from '../../icons';
21-
import { useApi } from '@backstage/core-plugin-api';
22-
import { stackoverflowteamsApiRef } from '../../api';
2321

2422
const useStyles = makeStyles((theme: Theme) => ({
2523
filters: {
@@ -190,7 +188,6 @@ const useEnhancedSearch = () => {
190188
return;
191189
}
192190

193-
// If we have cached data for this server page, don't refetch
194191
if (searchCache[cacheKey]) {
195192
return;
196193
}
@@ -269,10 +266,7 @@ const useEnhancedSearch = () => {
269266

270267
export const StackOverflowQuestions = () => {
271268
const classes = useStyles();
272-
const stackOverflowTeamsApi = useApi(stackoverflowteamsApiRef);
273-
const [baseUrl, setBaseUrl] = useState<string>('');
274269

275-
// Questions data using enhanced hook
276270
const {
277271
data: questionsData,
278272
loading: questionsLoading,
@@ -283,15 +277,13 @@ export const StackOverflowQuestions = () => {
283277
fetchUnansweredQuestions
284278
} = useStackOverflowData('questions');
285279

286-
// Enhanced search hook
287280
const {
288281
enhancedSearch,
289282
getSearchDisplayData,
290283
clearSearch,
291284
clearSearchCache,
292285
} = useEnhancedSearch();
293286

294-
// State management
295287
const [searchTerm, setSearchTerm] = useState('');
296288
const [activeFilter, setActiveFilter] = useState<string>('active');
297289
const [currentPage, setCurrentPage] = useState(1);
@@ -310,16 +302,10 @@ export const StackOverflowQuestions = () => {
310302
clearSearchCacheRef.current = clearSearchCache;
311303
});
312304

313-
// Debounce search term
314305
const debouncedSearchTerm = useDebounce(searchTerm, 500);
315306

316-
// Determine if we're in search mode
317307
const isSearchMode = !!searchTerm.trim();
318308

319-
useEffect(() => {
320-
stackOverflowTeamsApi.getBaseUrl().then(url => setBaseUrl(url));
321-
}, [stackOverflowTeamsApi]);
322-
323309
// Calculate required server page for current client page (only for questions)
324310
const requiredServerPage = useMemo(() => {
325311
return calculateServerPage(questionsPage, SERVER_ITEMS_PER_PAGE);
@@ -523,10 +509,9 @@ export const StackOverflowQuestions = () => {
523509
/>
524510
</Box>
525511

526-
{/* Top pagination controls - always visible */}
527512
<PaginationControls />
528513

529-
{/* Only show filters when not in search mode - always visible */}
514+
{/* Only show filters when not in search mode */}
530515
{!isSearchMode && (
531516
<Paper className={classes.filters}>
532517
<ButtonGroup className={classes.buttonGroup}>
@@ -550,28 +535,19 @@ export const StackOverflowQuestions = () => {
550535
: `Showing ${displayInfo.currentPageData.length} of ${displayInfo.totalCount} results`}
551536
</Typography>
552537

553-
{/* Show loading skeleton for initial loads and when switching pages */}
554538
{displayInfo.loading && <LoadingSkeleton />}
555539

556-
{/* Show content only when not loading */}
557540
{!displayInfo.loading && (
558541
<>
559542
{/* No results */}
560-
{displayInfo.currentPageData.length === 0 && (
543+
{displayInfo.currentPageData && displayInfo.totalCount === 0 && (
561544
<Box textAlign="center" py={4}>
562545
<Typography variant="body1" gutterBottom>
563546
{searchTerm.trim()
564547
? `No questions found matching "${searchTerm}"`
565548
: "No questions found"
566549
}
567550
</Typography>
568-
{searchTerm.trim() && (
569-
<Link
570-
to={`${baseUrl}/search?q=${encodeURIComponent(searchTerm)}`}
571-
>
572-
However, you might find more questions on your Stack Overflow Team.
573-
</Link>
574-
)}
575551
</Box>
576552
)}
577553

@@ -603,23 +579,11 @@ export const StackOverflowQuestions = () => {
603579
</Grid>
604580
))}
605581
</Grid>
606-
607-
<Box mt={2}>
608-
<Link to={searchTerm.trim()
609-
? `${baseUrl}/search?q=${encodeURIComponent(searchTerm)}`
610-
: `${baseUrl}/questions`}
611-
>
612-
<Typography variant='body1'>
613-
Explore more questions on your Stack Overflow Team
614-
</Typography>
615-
</Link>
616-
</Box>
617582
</>
618583
)}
619584
</>
620585
)}
621586

622-
{/* Bottom pagination controls - always visible */}
623587
<PaginationControls />
624588
</div>
625589
);

plugins/stack-overflow-teams/src/components/StackOverflow/StackOverflowSearchResultListItem.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ export const StackOverflowSearchResultListItem = (
118118
}
119119
/>
120120

121-
{/* Author section - moved outside of ListItemText to avoid nested links */}
122121
<Box display="flex" alignItems="center" mt={1}>
123122
{/* Author Avatar */}
124123
{result.avatar && (

plugins/stack-overflow-teams/src/components/StackOverflow/StackOverflowTags.tsx

Lines changed: 108 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useEffect, useState, useCallback } from 'react';
22
import { Link, Progress, ResponseErrorPanel } from '@backstage/core-components';
33
import { useStackOverflowData } from './hooks/';
44
import {
@@ -17,23 +17,28 @@ import { useApi } from '@backstage/core-plugin-api';
1717
const StackOverflowTagList = ({
1818
tags,
1919
searchTerm,
20-
baseUrl, // Use the baseUrl prop
20+
isApiSearch = false,
21+
isSearching = false,
2122
}: {
2223
tags: Tag[];
2324
searchTerm: string;
2425
baseUrl: string;
26+
isApiSearch?: boolean;
27+
isSearching?: boolean;
2528
}) => {
29+
if (isSearching) {
30+
return null;
31+
}
32+
2633
if (tags.length === 0) {
2734
return (
2835
<Box textAlign="center" py={4}>
2936
<Typography variant="body1" gutterBottom>
30-
No matching tags were found for "{searchTerm}"
37+
{isApiSearch
38+
? `No tags found for "${searchTerm}" on your Stack Overflow Team`
39+
: `No matching tags were found for "${searchTerm}"`
40+
}
3141
</Typography>
32-
<Link
33-
to={`${baseUrl}/tags?query=${encodeURIComponent(searchTerm)}`}
34-
>
35-
However, you might find this tag on your Stack Overflow Team.
36-
</Link>
3742
</Box>
3843
);
3944
}
@@ -49,40 +54,91 @@ const StackOverflowTagList = ({
4954
/>
5055
</Link>
5156
))}
52-
53-
<Grid item xs={12}>
54-
<Link to={ searchTerm ? `${baseUrl}/tags?query=${encodeURIComponent(searchTerm)}` :`${baseUrl}/tags`}>
55-
<Typography variant='body1'>
56-
Explore more tags on your Stack Overflow Team
57-
</Typography>
58-
</Link>
59-
</Grid>
6057
</Grid>
6158
);
6259
};
6360

6461
export const StackOverflowTags = () => {
65-
const { data, loading, error } = useStackOverflowData('tags');
62+
const { data, loading, error, fetchData } = useStackOverflowData('tags');
6663
const [searchTerm, setSearchTerm] = useState('');
64+
const [apiSearchResults, setApiSearchResults] = useState<Tag[] | null>(null);
65+
const [apiSearchLoading, setApiSearchLoading] = useState(false);
66+
const [apiSearchError, setApiSearchError] = useState<Error | null>(null);
67+
const [hasAttemptedApiSearch, setHasAttemptedApiSearch] = useState(false);
6768
const stackOverflowTeamsApi = useApi(stackoverflowteamsApiRef);
6869

6970
const [baseUrl, setBaseUrl] = useState<string>('');
71+
7072
useEffect(() => {
7173
stackOverflowTeamsApi.getBaseUrl().then(url => setBaseUrl(url));
7274
}, [stackOverflowTeamsApi]);
7375

74-
if (loading) {
75-
return <Progress />;
76-
}
76+
useEffect(() => {
77+
fetchData();
78+
}, [fetchData]);
7779

78-
if (error) {
79-
return <ResponseErrorPanel error={error} />;
80-
}
80+
// Debounced API search function
81+
const searchTagsViaApi = useCallback(async (searchQuery: string) => {
82+
if (!searchQuery.trim()) {
83+
setApiSearchResults(null);
84+
setHasAttemptedApiSearch(false);
85+
return;
86+
}
8187

82-
const filteredTags = (data?.tags || []).filter(tag =>
88+
setApiSearchLoading(true);
89+
setApiSearchError(null);
90+
setHasAttemptedApiSearch(false);
91+
92+
try {
93+
const response = await stackOverflowTeamsApi.getTags(searchQuery);
94+
setApiSearchResults(response.items || []);
95+
} catch (err) {
96+
setApiSearchError(err as Error);
97+
setApiSearchResults([]);
98+
} finally {
99+
setApiSearchLoading(false);
100+
setHasAttemptedApiSearch(true);
101+
}
102+
}, [stackOverflowTeamsApi]);
103+
104+
// Debounce the search to avoid too many API calls
105+
useEffect(() => {
106+
const timer = setTimeout(() => {
107+
const localFilteredTags = (data?.tags || []).filter(tag =>
108+
tag.name.toLowerCase().includes(searchTerm.toLowerCase()),
109+
);
110+
111+
// If no local results and we have a search term, try API search
112+
if (localFilteredTags.length === 0 && searchTerm.trim()) {
113+
searchTagsViaApi(searchTerm);
114+
} else {
115+
// Reset API search results when we have local results or no search term
116+
setApiSearchResults(null);
117+
setApiSearchError(null);
118+
setHasAttemptedApiSearch(false);
119+
}
120+
}, 300); // 300ms debounce
121+
122+
return () => clearTimeout(timer);
123+
}, [searchTerm, data?.tags, searchTagsViaApi]);
124+
125+
const localFilteredTags = (data?.tags || []).filter(tag =>
83126
tag.name.toLowerCase().includes(searchTerm.toLowerCase()),
84127
);
85128

129+
const shouldShowApiResults = localFilteredTags.length === 0 && searchTerm.trim() && hasAttemptedApiSearch && apiSearchResults !== null;
130+
const tagsToShow = shouldShowApiResults ? apiSearchResults : localFilteredTags;
131+
const isShowingApiResults = shouldShowApiResults;
132+
const currentLoading = apiSearchLoading;
133+
const currentError = apiSearchError;
134+
135+
const shouldShowResults = !currentLoading && !currentError && (
136+
// Show local results if we have them
137+
localFilteredTags.length > 0 ||
138+
// Show API results if we have attempted API search and got results
139+
(hasAttemptedApiSearch && apiSearchResults !== null)
140+
);
141+
86142
return (
87143
<div>
88144
<Box mb={2}>
@@ -102,13 +158,35 @@ export const StackOverflowTags = () => {
102158
/>
103159
</Box>
104160

105-
<StackOverflowTagList
106-
tags={filteredTags}
107-
searchTerm={searchTerm}
108-
baseUrl={baseUrl}
109-
/>
161+
{/* Show initial loading state */}
162+
{loading && <Progress />}
163+
164+
{/* Show initial error state */}
165+
{error && <ResponseErrorPanel error={error} />}
166+
167+
{/* Only show content when initial data is loaded */}
168+
{!loading && !error && (
169+
<>
170+
{/* Show API search loading */}
171+
{currentLoading && <Progress />}
172+
173+
{/* Show API search error */}
174+
{currentError && <ResponseErrorPanel error={currentError} />}
175+
176+
{/* Show results when appropriate */}
177+
{shouldShowResults && (
178+
<StackOverflowTagList
179+
tags={tagsToShow || []}
180+
searchTerm={searchTerm}
181+
baseUrl={baseUrl}
182+
isApiSearch={!!isShowingApiResults}
183+
isSearching={currentLoading}
184+
/>
185+
)}
186+
</>
187+
)}
110188
</div>
111189
);
112190
};
113191

114-
export default StackOverflowTags;
192+
export default StackOverflowTags;

0 commit comments

Comments
 (0)