Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref(app-starts): Generalize screen table component #69839

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import useRouter from 'sentry/utils/useRouter';
import {prepareQueryForLandingPage} from 'sentry/views/performance/data';
import {AverageComparisonChart} from 'sentry/views/performance/mobile/appStarts/screens/averageComparisonChart';
import {CountChart} from 'sentry/views/performance/mobile/appStarts/screens/countChart';
import {ScreensTable} from 'sentry/views/performance/mobile/appStarts/screens/screensTable';
import {AppStartScreens} from 'sentry/views/performance/mobile/appStarts/screens/screensTable';
import {COLD_START_TYPE} from 'sentry/views/performance/mobile/appStarts/screenSummary/startTypeSelector';
import {
getFreeTextFromQuery,
Expand Down Expand Up @@ -237,7 +237,7 @@ function AppStartup({additionalFilters, chartHeight}: Props) {
)
}
/>
<ScreensTable
<AppStartScreens
eventView={tableEventView}
data={topTransactionsData}
isLoading={topTransactionsLoading}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {render, screen} from 'sentry-test/reactTestingLibrary';

import EventView from 'sentry/utils/discover/eventView';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import {AppStartScreens} from 'sentry/views/performance/mobile/appStarts/screens/screensTable';
import {useReleaseSelection} from 'sentry/views/starfish/queries/useReleases';

jest.mock('sentry/views/starfish/queries/useReleases');

jest.mocked(useReleaseSelection).mockReturnValue({
primaryRelease: 'com.example.vu.android@2.10.5',
isLoading: false,
secondaryRelease: 'com.example.vu.android@2.10.3+42',
});

function getMockEventView({fields}) {
return new EventView({
id: '1',
name: 'mock query',
fields,

sorts: [],
query: '',
project: [],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: [],
additionalConditions: new MutableSearch(''),
createdBy: undefined,
interval: undefined,
display: '',
team: [],
topEvents: undefined,
yAxis: undefined,
});
}

describe('AppStartScreens', () => {
it('renders the correct headers', () => {
render(
<AppStartScreens
data={{
data: [],
meta: {
fields: [],
},
}}
eventView={getMockEventView({fields: []})}
isLoading={false}
pageLinks={undefined}
/>
);

expect(screen.getByRole('columnheader', {name: 'Screen'})).toBeInTheDocument();
expect(
screen.getByRole('columnheader', {name: 'Cold Start (R1)'})
).toBeInTheDocument();
expect(
screen.getByRole('columnheader', {name: 'Cold Start (R2)'})
).toBeInTheDocument();
expect(screen.getByRole('columnheader', {name: 'Change'})).toBeInTheDocument();
expect(
screen.getByRole('columnheader', {name: 'Type Breakdown'})
).toBeInTheDocument();
expect(screen.getByRole('columnheader', {name: 'Count'})).toBeInTheDocument();
});

it('renders custom transaction and breakdown fields', () => {
render(
<AppStartScreens
data={{
data: [
{
id: '1',
transaction: 'Screen 1',
'avg_if(measurements.app_start_cold,release,com.example.vu.android@2.10.5)': 100,
'avg_if(measurements.app_start_cold,release,com.example.vu.android@2.10.3+42)': 200,
'avg_compare(measurements.app_start_cold,release,com.example.vu.android@2.10.5,com.example.vu.android@2.10.3+42)': 50,
app_start_breakdown: 'breakdown',
'count_starts(measurements.app_start_cold)': 10,
},
],
meta: {
fields: [],
},
}}
eventView={getMockEventView({fields: []})}
isLoading={false}
pageLinks={undefined}
/>
);

expect(screen.getByRole('link', {name: 'Screen 1'})).toBeInTheDocument();
expect(screen.getByTestId('app-start-breakdown')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import {Fragment} from 'react';
import * as qs from 'query-string';

import type {GridColumnHeader} from 'sentry/components/gridEditable';
import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
import SortLink from 'sentry/components/gridEditable/sortLink';
import Link from 'sentry/components/links/link';
import Pagination from 'sentry/components/pagination';
import {t} from 'sentry/locale';
import type {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery';
import type {MetaType} from 'sentry/utils/discover/eventView';
import type {TableData} from 'sentry/utils/discover/discoverQuery';
import type EventView from 'sentry/utils/discover/eventView';
import {isFieldSortable} from 'sentry/utils/discover/eventView';
import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
import {fieldAlignment} from 'sentry/utils/discover/fields';
import {decodeScalar} from 'sentry/utils/queryString';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {normalizeUrl} from 'sentry/utils/withDomainRequired';
import TopResultsIndicator from 'sentry/views/discover/table/topResultsIndicator';
import Breakdown from 'sentry/views/performance/mobile/appStarts/screens/breakdown';
import {COLD_START_TYPE} from 'sentry/views/performance/mobile/appStarts/screenSummary/startTypeSelector';
import {ScreensTable} from 'sentry/views/performance/mobile/components/screensTable';
import {TOP_SCREENS} from 'sentry/views/performance/mobile/screenload/screens';
import {COLD_START_COLOR, WARM_START_COLOR} from 'sentry/views/starfish/colors';
import {
Expand All @@ -36,7 +29,7 @@ type Props = {
pageLinks: string | undefined;
};

export function ScreensTable({data, eventView, isLoading, pageLinks}: Props) {
export function AppStartScreens({data, eventView, isLoading, pageLinks}: Props) {
const location = useLocation();
const organization = useOrganization();
const {primaryRelease, secondaryRelease} = useReleaseSelection();
Expand Down Expand Up @@ -71,9 +64,9 @@ export function ScreensTable({data, eventView, isLoading, pageLinks}: Props) {
'count_starts(measurements.app_start_warm)': t('Count'),
};

function renderBodyCell(column, row): React.ReactNode {
if (!data?.meta || !data?.meta.fields) {
return row[column.key];
function renderBodyCell(column, row): React.ReactNode | null {
narsaynorath marked this conversation as resolved.
Show resolved Hide resolved
if (!data) {
return null;
}

const index = data.data.indexOf(row);
Expand Down Expand Up @@ -107,6 +100,7 @@ export function ScreensTable({data, eventView, isLoading, pageLinks}: Props) {
if (field === 'app_start_breakdown') {
return (
<Breakdown
data-test-id="app-start-breakdown"
row={row}
breakdownGroups={[
{
Expand All @@ -124,87 +118,31 @@ export function ScreensTable({data, eventView, isLoading, pageLinks}: Props) {
);
}

const renderer = getFieldRenderer(column.key, data?.meta.fields, false);
return renderer(row, {
location,
organization,
unit: data?.meta.units?.[column.key],
});
}

function renderHeadCell(
column: GridColumnHeader,
tableMeta?: MetaType
): React.ReactNode {
const fieldType = tableMeta?.fields?.[column.key];
const alignment = fieldAlignment(column.key as string, fieldType);
const field = {
field: column.key as string,
width: column.width,
};

function generateSortLink() {
if (!tableMeta) {
return undefined;
}

const nextEventView = eventView.sortOnField(field, tableMeta);
const queryStringObject = nextEventView.generateQueryStringObject();

return {
...location,
query: {...location.query, sort: queryStringObject.sort},
};
}

const currentSort = eventView.sortForField(field, tableMeta);
const currentSortKind = currentSort ? currentSort.kind : undefined;
const canSort = isFieldSortable(field, tableMeta);

const sortLink = (
<SortLink
align={alignment}
title={column.name}
direction={currentSortKind}
canSort={canSort}
generateSortLink={generateSortLink}
/>
);
return sortLink;
return null;
}

return (
<Fragment>
<GridEditable
isLoading={isLoading}
data={data?.data as TableDataRow[]}
columnOrder={[
'transaction',
`avg_if(measurements.app_start_${startType},release,${primaryRelease})`,
`avg_if(measurements.app_start_${startType},release,${secondaryRelease})`,
`avg_compare(measurements.app_start_${startType},release,${primaryRelease},${secondaryRelease})`,
'app_start_breakdown',
`count_starts(measurements.app_start_${startType})`,
].map(columnKey => {
return {
key: columnKey,
name: columnNameMap[columnKey],
width: COL_WIDTH_UNDEFINED,
};
})}
columnSortBy={[
{
key: `count_starts_measurements_app_start_${startType}`,
order: 'desc',
},
]}
location={location}
grid={{
renderHeadCell: column => renderHeadCell(column, data?.meta),
renderBodyCell,
}}
/>
<Pagination pageLinks={pageLinks} />
</Fragment>
<ScreensTable
columnNameMap={columnNameMap}
data={data}
eventView={eventView}
isLoading={isLoading}
pageLinks={pageLinks}
columnOrder={[
'transaction',
`avg_if(measurements.app_start_${startType},release,${primaryRelease})`,
`avg_if(measurements.app_start_${startType},release,${secondaryRelease})`,
`avg_compare(measurements.app_start_${startType},release,${primaryRelease},${secondaryRelease})`,
'app_start_breakdown',
`count_starts(measurements.app_start_${startType})`,
]}
defaultSort={[
{
key: `count_starts_measurements_app_start_${startType}`,
order: 'desc',
},
]}
customBodyCellRenderer={renderBodyCell}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {render, screen} from 'sentry-test/reactTestingLibrary';

import EventView from 'sentry/utils/discover/eventView';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import {ScreensTable} from 'sentry/views/performance/mobile/components/screensTable';

function getMockEventView({fields}) {
return new EventView({
id: '1',
name: 'mock query',
fields,

sorts: [],
query: '',
project: [],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: [],
additionalConditions: new MutableSearch(''),
createdBy: undefined,
interval: undefined,
display: '',
team: [],
topEvents: undefined,
yAxis: undefined,
});
}

describe('ScreensTable', () => {
it('renders table header cells with translated names', () => {
render(
<ScreensTable
columnNameMap={{
transaction: 'Screen',
}}
columnOrder={['transaction']}
data={{
data: [{id: '1', transaction: 'Screen 1'}],
meta: {},
}}
defaultSort={[]}
eventView={getMockEventView({fields: [{field: 'transaction'}]})}
isLoading={false}
pageLinks={undefined}
/>
);

expect(screen.getByText('Screen')).toBeInTheDocument();
expect(screen.queryByText('transaction')).not.toBeInTheDocument();
});

it('renders body cells with custom renderer if applicable', () => {
render(
<ScreensTable
columnNameMap={{
transaction: 'Screen',
}}
columnOrder={['transaction', 'non-custom']}
data={{
data: [
{id: '1', transaction: 'Screen 1', 'non-custom': 'non customized value'},
],
meta: {fields: {transaction: 'string'}},
}}
defaultSort={[]}
eventView={getMockEventView({
fields: [{field: 'transaction'}, {field: 'non-custom'}],
})}
isLoading={false}
pageLinks={undefined}
customBodyCellRenderer={(column, row) => {
if (column.key === 'transaction') {
return `Custom rendered ${row.transaction}`;
}

return null;
}}
/>
);

expect(screen.getByText('Custom rendered Screen 1')).toBeInTheDocument();
expect(screen.getByText('non customized value')).toBeInTheDocument();
});
});