Skip to content

Commit 28a4dd2

Browse files
authored
feat(custom-views): Add custom views to issue stream behind feature flag (#75883)
This PR releases the custom views feature into the issue stream under a feature flag. Flagged users will also see the "Custom Search" button has been removed from the issue stream in favor of custom views. The bulk of this new component exists in `<CustomViewsIssueListHeader/>`, whose parent component is in overview.tsx. Several changes have also been made to the base draggable tab components to iron out any compatibility issues that did not appear during sandbox development. Things that are still broken that I am actively working on: * ~Tab Renaming is both ugly and broken (can't type in spaces)~ (#76247) * Tab overflow logic does not exist at all * QueryCounts do not work for any tabs with queries that do not match queries of previous default tabs * Some queries that are functionally the same but off by a space register as different, thus triggering the "unsaved changes" state. * Tabs do not animate in when added, nor do they animate out when deleted * No loading or failure state for tabs request * ~Stories is busted as a result of all of the changes made to base tabs components~ (deleted stories, this component is not, and was not meant to be reusable)
1 parent c96f056 commit 28a4dd2

13 files changed

+498
-222
lines changed

static/app/components/draggableTabs/draggableTabList.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ function BaseDraggableTabList({
3030
outerWrapStyles,
3131
onReorder,
3232
onAddView,
33-
showTempTab = false,
3433
tabVariant = 'filled',
3534
...props
3635
}: BaseDraggableTabListProps) {
@@ -72,7 +71,7 @@ function BaseDraggableTabList({
7271
useEffect(() => {
7372
setTabListState(state);
7473
// eslint-disable-next-line react-hooks/exhaustive-deps
75-
}, [state.disabledKeys, state.selectedItem, state.selectedKey, props.children]);
74+
}, [state.selectedItem, state.selectedKey, props.children]);
7675

7776
// Detect tabs that overflow from the wrapper and put them in an overflow menu
7877
const tabItemsRef = useRef<Record<string | number, HTMLLIElement | null>>({});
@@ -119,11 +118,14 @@ function BaseDraggableTabList({
119118
variant={tabVariant}
120119
/>
121120
</Reorder.Item>
122-
{(state.selectedKey === 'temporary-tab' ||
123-
(state.selectedKey !== item.key &&
124-
state.collection.getKeyAfter(item.key) !== state.selectedKey)) && (
125-
<TabDivider layout />
126-
)}
121+
<TabDivider
122+
layout
123+
active={
124+
state.selectedKey === 'temporary-tab' ||
125+
(state.selectedKey !== item.key &&
126+
state.collection.getKeyAfter(item.key) !== state.selectedKey)
127+
}
128+
/>
127129
</Fragment>
128130
))}
129131
</TabListWrap>
@@ -134,9 +136,9 @@ function BaseDraggableTabList({
134136
{t('Add View')}
135137
</AddViewButton>
136138
</MotionWrapper>
137-
<TabDivider layout />
139+
<TabDivider layout active />
138140
<MotionWrapper layout>
139-
{showTempTab && tempTab && (
141+
{tempTab && (
140142
<Tab
141143
key={tempTab.key}
142144
item={tempTab}
@@ -173,12 +175,7 @@ export interface DraggableTabListProps
173175
* To be used as a direct child of the <Tabs /> component. See example usage
174176
* in tabs.stories.js
175177
*/
176-
export function DraggableTabList({
177-
items,
178-
onAddView,
179-
showTempTab,
180-
...props
181-
}: DraggableTabListProps) {
178+
export function DraggableTabList({items, onAddView, ...props}: DraggableTabListProps) {
182179
const collection = useCollection({items, ...props}, collectionFactory);
183180

184181
const parsedItems = useMemo(
@@ -199,7 +196,6 @@ export function DraggableTabList({
199196
<BaseDraggableTabList
200197
items={parsedItems}
201198
onAddView={onAddView}
202-
showTempTab={showTempTab}
203199
disabledKeys={disabledKeys}
204200
{...props}
205201
>
@@ -210,11 +206,18 @@ export function DraggableTabList({
210206

211207
DraggableTabList.Item = Item;
212208

213-
const TabDivider = styled(motion.div)`
214-
height: 50%;
215-
width: 1px;
216-
border-radius: 6px;
217-
background-color: ${p => p.theme.gray200};
209+
const TabDivider = styled(motion.div)<{active: boolean}>`
210+
${p =>
211+
p.active &&
212+
`
213+
background-color: ${p.theme.gray200};
214+
height: 50%;
215+
width: 1px;
216+
border-radius: 6px;
217+
margin-right: ${space(0.5)};
218+
`}
219+
margin-top: 1px;
220+
margin-left: ${space(0.5)};
218221
`;
219222

220223
const TabListOuterWrap = styled('div')<{
@@ -252,14 +255,13 @@ const TabListWrap = styled('ul')`
252255
const AddViewButton = styled(Button)`
253256
display: flex;
254257
color: ${p => p.theme.gray300};
255-
padding-right: ${space(0.5)};
256-
margin: 4px 2px 2px 2px;
257258
font-weight: normal;
259+
padding: ${space(0.5)};
260+
transform: translateY(1px);
258261
`;
259262

260263
const StyledIconAdd = styled(IconAdd)`
261264
margin-right: 4px;
262-
margin-left: 2px;
263265
`;
264266

265267
const MotionWrapper = styled(motion.div)`

static/app/components/layouts/thirds.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ export const Page = styled('main')<{withPadding?: boolean}>`
2222
*
2323
* Use `noActionWrap` to disable wrapping if there are minimal actions.
2424
*/
25-
export const Header = styled('header')<{noActionWrap?: boolean}>`
25+
export const Header = styled('header')<{
26+
borderStyle?: 'dashed' | 'solid';
27+
noActionWrap?: boolean;
28+
}>`
2629
display: grid;
2730
grid-template-columns: ${p =>
2831
!p.noActionWrap ? 'minmax(0, 1fr)' : 'minmax(0, 1fr) auto'};
2932
3033
padding: ${space(2)} ${space(2)} 0 ${space(2)};
3134
background-color: transparent;
32-
border-bottom: 1px solid ${p => p.theme.border};
35+
border-bottom: 1px ${p => p.borderStyle ?? 'solid'} ${p => p.theme.border};
3336
3437
@media (min-width: ${p => p.theme.breakpoints.medium}) {
3538
padding: ${space(2)} ${space(4)} 0 ${space(4)};

static/app/components/tabs/tab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export interface BaseTabProps {
5353
tabProps: DOMAttributes<FocusableElement>;
5454
/**
5555
* This controls the border style of the tab. Only active when
56-
* `variant=filled` since other variants do not have a border
56+
* `variant='filled'` since other variants do not have a border
5757
*/
5858
borderStyle?: 'solid' | 'dashed';
5959
to?: string;

static/app/utils/withSavedSearches.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,24 @@ function withSavedSearches<P extends InjectedSavedSearchesProps>(
2424
props: Omit<P, keyof InjectedSavedSearchesProps> & Partial<InjectedSavedSearchesProps>
2525
) {
2626
const organization = useOrganization();
27-
const {data: savedSearches, isLoading} = useFetchSavedSearchesForOrg({
28-
orgSlug: organization.slug,
29-
});
27+
const {data: savedSearches, isLoading} = useFetchSavedSearchesForOrg(
28+
{
29+
orgSlug: organization.slug,
30+
},
31+
{enabled: !organization.features.includes('issue-stream-custom-views')}
32+
);
33+
3034
const params = useParams();
3135
const selectedSavedSearch = useSelectedSavedSearch();
3236

3337
return (
3438
<WrappedComponent
3539
{...(props as P)}
3640
savedSearches={props.savedSearches ?? savedSearches}
37-
savedSearchLoading={props.savedSearchLoading ?? isLoading}
41+
savedSearchLoading={
42+
!organization.features.includes('issue-stream-custom-views') &&
43+
(props.savedSearchLoading ?? isLoading)
44+
}
3845
savedSearch={props.savedSearch ?? selectedSavedSearch}
3946
selectedSearchId={params.searchId ?? null}
4047
/>

0 commit comments

Comments
 (0)