Skip to content

Commit

Permalink
ref(performance): Separate header from content in transaction events (#…
Browse files Browse the repository at this point in the history
…28346)

Continuing from #28343, this separates the header from the content on the
transaction events tab.
  • Loading branch information
Zylphrex authored Sep 1, 2021
1 parent 0d8c31f commit 774234f
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 352 deletions.
Original file line number Diff line number Diff line change
@@ -1,214 +1,95 @@
import * as React from 'react';
import {Fragment} from 'react';
import {browserHistory} from 'react-router';
import styled from '@emotion/styled';
import {Location} from 'history';
import omit from 'lodash/omit';

import Alert from 'app/components/alert';
import Button from 'app/components/button';
import {CreateAlertFromViewButton} from 'app/components/createAlertButton';
import DropdownControl, {DropdownItem} from 'app/components/dropdownControl';
import SearchBar from 'app/components/events/searchBar';
import GlobalSdkUpdateAlert from 'app/components/globalSdkUpdateAlert';
import * as Layout from 'app/components/layouts/thirds';
import LoadingIndicator from 'app/components/loadingIndicator';
import {getParams} from 'app/components/organizations/globalSelectionHeader/getParams';
import {IconFlag} from 'app/icons';
import {t} from 'app/locale';
import space from 'app/styles/space';
import {Organization, Project} from 'app/types';
import {Organization} from 'app/types';
import EventView from 'app/utils/discover/eventView';
import {WebVital} from 'app/utils/discover/fields';
import {decodeScalar} from 'app/utils/queryString';
import {MutableSearch} from 'app/utils/tokenizeSearch';
import {Actions, updateQuery} from 'app/views/eventsV2/table/cellAction';
import {TableColumn} from 'app/views/eventsV2/table/types';

import Filter, {filterToSearchConditions, SpanOperationBreakdownFilter} from '../filter';
import TransactionHeader from '../header';
import Tab from '../tabs';
import {SetStateAction} from '../types';

import EventsTable from './eventsTable';
import {EventsDisplayFilterName, getEventsFilterOptions} from './utils';

type Props = {
location: Location;
organization: Organization;
eventView: EventView;
transactionName: string;
organization: Organization;
projects: Project[];
spanOperationBreakdownFilter: SpanOperationBreakdownFilter;
onChangeSpanOperationBreakdownFilter: (newFilter: SpanOperationBreakdownFilter) => void;
eventsDisplayFilterName: EventsDisplayFilterName;
onChangeEventsDisplayFilter: (eventsDisplayFilterName: EventsDisplayFilterName) => void;
percentileValues?: Record<EventsDisplayFilterName, number>;
isLoading: boolean;
webVital?: WebVital;
setError: SetStateAction<string | undefined>;
};

type State = {
incompatibleAlertNotice: React.ReactNode;
error: string | undefined;
};

class EventsPageContent extends React.Component<Props, State> {
state: State = {
incompatibleAlertNotice: null,
error: undefined,
};

handleCellAction = (column: TableColumn<React.ReactText>) => {
return (action: Actions, value: React.ReactText) => {
const {eventView, location} = this.props;

const searchConditions = new MutableSearch(eventView.query);

// remove any event.type queries since it is implied to apply to only transactions
searchConditions.removeFilter('event.type');

// no need to include transaction as its already in the query params
searchConditions.removeFilter('transaction');

updateQuery(searchConditions, action, column, value);

browserHistory.push({
pathname: location.pathname,
query: {
...location.query,
cursor: undefined,
query: searchConditions.formatString(),
},
});
};
};

handleIncompatibleQuery: React.ComponentProps<
typeof CreateAlertFromViewButton
>['onIncompatibleQuery'] = (incompatibleAlertNoticeFn, _errors) => {
const incompatibleAlertNotice = incompatibleAlertNoticeFn(() =>
this.setState({incompatibleAlertNotice: null})
);
this.setState({incompatibleAlertNotice});
};
function EventsContent(props: Props) {
const {
location,
organization,
eventView: originalEventView,
transactionName,
spanOperationBreakdownFilter,
webVital,
setError,
} = props;

renderError() {
const {error} = this.state;
const eventView = originalEventView.clone();

if (!error) {
return null;
}
const transactionsListTitles = [
t('event id'),
t('user'),
t('operation duration'),
t('total duration'),
t('trace id'),
t('timestamp'),
];

return (
<StyledAlert type="error" icon={<IconFlag size="md" />}>
{error}
</StyledAlert>
);
if (webVital) {
transactionsListTitles.splice(3, 0, t(webVital));
}

setError = (error: string | undefined) => {
this.setState({error});
};
const spanOperationBreakdownConditions = filterToSearchConditions(
spanOperationBreakdownFilter,
location
);

render() {
const {eventView, location, organization, projects, transactionName, isLoading} =
this.props;
const {incompatibleAlertNotice} = this.state;
if (spanOperationBreakdownConditions) {
eventView.query = `${eventView.query} ${spanOperationBreakdownConditions}`.trim();
transactionsListTitles.splice(2, 1, t(`${spanOperationBreakdownFilter} duration`));
}

return (
<Fragment>
<TransactionHeader
return (
<Layout.Main fullWidth>
<Search {...props} />
<StyledTable>
<EventsTable
eventView={eventView}
location={location}
organization={organization}
projects={projects}
location={location}
setError={setError}
columnTitles={transactionsListTitles}
transactionName={transactionName}
currentTab={Tab.Events}
hasWebVitals="maybe"
handleIncompatibleQuery={this.handleIncompatibleQuery}
/>
<Layout.Body>
<StyledSdkUpdatesAlert />
{this.renderError()}
{incompatibleAlertNotice && (
<Layout.Main fullWidth>{incompatibleAlertNotice}</Layout.Main>
)}
<Layout.Main fullWidth>
{isLoading ? (
<LoadingIndicator />
) : (
<Body {...this.props} setError={this.setError} />
)}
</Layout.Main>
</Layout.Body>
</Fragment>
);
}
}

class Body extends React.Component<
Props & {setError: (error: string | undefined) => void},
State
> {
render() {
let {eventView} = this.props;
const {
location,
organization,
transactionName,
spanOperationBreakdownFilter,
eventsDisplayFilterName,
onChangeEventsDisplayFilter,
setError,
webVital,
} = this.props;
const transactionsListTitles = [
t('event id'),
t('user'),
t('operation duration'),
t('total duration'),
t('trace id'),
t('timestamp'),
];

if (webVital) {
transactionsListTitles.splice(3, 0, t(webVital));
}

const spanOperationBreakdownConditions = filterToSearchConditions(
spanOperationBreakdownFilter,
location
);

if (spanOperationBreakdownConditions) {
eventView = eventView.clone();
eventView.query = `${eventView.query} ${spanOperationBreakdownConditions}`.trim();
transactionsListTitles.splice(2, 1, t(`${spanOperationBreakdownFilter} duration`));
}

return (
<React.Fragment>
<Search
{...this.props}
onChangeEventsDisplayFilter={onChangeEventsDisplayFilter}
eventsDisplayFilterName={eventsDisplayFilterName}
/>
<StyledTable>
<EventsTable
eventView={eventView}
organization={organization}
location={location}
setError={setError}
columnTitles={transactionsListTitles}
transactionName={transactionName}
/>
</StyledTable>
</React.Fragment>
);
}
</StyledTable>
</Layout.Main>
);
}

const Search = (props: Props) => {
function Search(props: Props) {
const {
eventView,
location,
Expand Down Expand Up @@ -283,19 +164,14 @@ const Search = (props: Props) => {
</SearchRowMenuItem>
</SearchWrapper>
);
};
}

const SearchWrapper = styled('div')`
display: flex;
width: 100%;
margin-bottom: ${space(3)};
`;

const StyledAlert = styled(Alert)`
grid-column: 1/3;
margin: 0;
`;

const StyledSearchBar = styled(SearchBar)`
flex-grow: 1;
`;
Expand All @@ -319,4 +195,4 @@ StyledSdkUpdatesAlert.defaultProps = {
Wrapper: p => <Layout.Main fullWidth {...p} />,
};

export default EventsPageContent;
export default EventsContent;
Loading

0 comments on commit 774234f

Please sign in to comment.