Skip to content

Commit

Permalink
✨ 461 adding the test results new UX (#491)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoscar committed May 16, 2022
1 parent 4fe5582 commit 1ebe755
Show file tree
Hide file tree
Showing 33 changed files with 537 additions and 299 deletions.
3 changes: 2 additions & 1 deletion web/cypress/integration/Trace/CreateAssertion.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ describe('Create Assertion', () => {
cy.get('#add-assertion-modal-ok-button').click();

cy.get('[data-cy=assertion-table]').should('have.lengthOf', 2);
cy.get('[data-cy=trace-drawer').click();

cy.get('[data-cy=test-results-assertion-table]').should('have.lengthOf', 2);
cy.get('[data-cy=assertion-card"]').should('be.visible');
});
});
48 changes: 48 additions & 0 deletions web/src/components/AssertionCard/AssertionCard.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {CloseCircleOutlined, EditOutlined} from '@ant-design/icons';
import {Typography} from 'antd';
import styled, {css} from 'styled-components';

export const AssertionCard = styled.div`
border-radius: 2px;
border: 1px solid rgba(3, 24, 73, 0.1);
`;

export const Header = styled.div`
display: flex;
background: #fbfbff;
border-bottom: 1px solid rgba(3, 24, 73, 0.1);
padding: 8px 14px;
justify-content: space-between;
border-radius: 2px 2px 0px 0px;
`;

export const Body = styled.div`
padding: 12px 14px;
display: flex;
flex-direction: column;
gap: 9px;
`;

export const SelectorListText = styled(Typography.Text).attrs({
strong: true,
})`
margin-right: 14px;
`;

export const SpanCountText = styled(Typography.Text)`
font-size: 12;
`;
const baseIcon = css`
font-size: 18px;
color: #61175e;
cursor: pointer;
`;

export const EditIcon = styled(EditOutlined)`
${baseIcon}
`;

export const DeleteIcon = styled(CloseCircleOutlined)`
${baseIcon}
margin-left: 12px;
`;
69 changes: 69 additions & 0 deletions web/src/components/AssertionCard/AssertionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {useCallback, useMemo} from 'react';
import {useStore} from 'react-flow-renderer';
import {IAssertionResult} from '../../types/Assertion.types';
import AssertionCheckRow from '../AssertionCheckRow';
import * as S from './AssertionCard.styled';

interface IAssertionCardProps {
assertionResult: IAssertionResult;
onSelectSpan(spanId: string): void;
onDelete(assertionId: string): void;
onEdit(assertionResult: IAssertionResult): void;
}

const AssertionCard: React.FC<IAssertionCardProps> = ({
assertionResult: {assertion: {selectors = [], assertionId = ''} = {}, spanListAssertionResult},
assertionResult,
onSelectSpan,
onDelete,
onEdit,
}) => {
const store = useStore();

const spanCountText = useMemo(() => {
const spanCount = spanListAssertionResult.length;

return `${spanCount} ${spanCount > 1 ? 'spans' : 'span'}`;
}, [spanListAssertionResult.length]);

const getIsSelectedSpan = useCallback(
(id: string): boolean => {
const {selectedElements} = store.getState();
const found = selectedElements ? selectedElements.find(element => element.id === id) : undefined;

return Boolean(found);
},
[store]
);

return (
<S.AssertionCard data-cy="assertion-card">
<S.Header>
<div>
<S.SelectorListText>{selectors.map(({value}) => value).join(' ')}</S.SelectorListText>
<S.SpanCountText>{spanCountText}</S.SpanCountText>
</div>
<div>
<S.EditIcon onClick={() => onEdit(assertionResult)} />
<S.DeleteIcon onClick={() => onDelete(assertionId)} />
</div>
</S.Header>
<S.Body>
{spanListAssertionResult.flatMap(({span, resultList}) =>
resultList.map(result => (
<AssertionCheckRow
key={`${result.propertyName}-${span.spanId}`}
result={result}
span={span}
onSelectSpan={onSelectSpan}
getIsSelectedSpan={getIsSelectedSpan}
assertionSelectorList={selectors.map(({value}) => value)}
/>
))
)}
</S.Body>
</S.AssertionCard>
);
};

export default AssertionCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import faker from '@faker-js/faker';
import {ComponentStory, ComponentMeta} from '@storybook/react';
import {LOCATION_NAME} from '../../../constants/Span.constants';
import {SELECTOR_DEFAULT_ATTRIBUTES} from '../../../constants/SemanticGroupNames.constants';

import AssertionCard from '../AssertionCard';
import SpanMock from '../../../models/__mocks__/Span.mock';

export default {
title: 'Assertion Card',
component: AssertionCard,
argTypes: {onSelectSpan: {action: 'noSelectSpan'}},
} as ComponentMeta<typeof AssertionCard>;

const Template: ComponentStory<typeof AssertionCard> = args => <AssertionCard {...args} />;

export const Default = Template.bind({});
Default.args = {
assertionResult: {
assertion: {
assertionId: faker.datatype.uuid(),
selectors: [
{
locationName: LOCATION_NAME.SPAN_ATTRIBUTES,
propertyName: faker.random.arrayElement(SELECTOR_DEFAULT_ATTRIBUTES[0].attributes),
value: 'http',
valueType: faker.random.word(),
},
{
locationName: LOCATION_NAME.SPAN_ATTRIBUTES,
propertyName: faker.random.arrayElement(SELECTOR_DEFAULT_ATTRIBUTES[0].attributes),
value: 'pokeshop',
valueType: faker.random.word(),
},
],
spanAssertions: [],
},
spanListAssertionResult: faker.datatype.array(faker.datatype.number({min: 1, max: 10})).map(() => ({
span: SpanMock.model(),
resultList: [],
})),
},
};
2 changes: 2 additions & 0 deletions web/src/components/AssertionCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export {default} from './AssertionCard';
21 changes: 21 additions & 0 deletions web/src/components/AssertionCardList/AssertionCardList.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import styled from 'styled-components';
import noResultsIcon from '../../assets/SpanAssertionsEmptyState.svg';

export const AssertionCardList = styled.div`
display: flex;
flex-direction: column;
gap: 24px;
`;

export const EmptyStateContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-top: 40px;
flex-direction: column;
gap: 14px;
`;

export const EmptyStateIcon = styled.img.attrs({
src: noResultsIcon,
})``;
63 changes: 63 additions & 0 deletions web/src/components/AssertionCardList/AssertionCardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {Typography} from 'antd';
import {useCallback} from 'react';
import {useDeleteAssertionMutation} from '../../redux/apis/Test.api';
import {IAssertionResult} from '../../types/Assertion.types';
import AssertionCard from '../AssertionCard/AssertionCard';
import {useCreateAssertionModal} from '../CreateAssertionModal/CreateAssertionModalProvider';
import * as S from './AssertionCardList.styled';

interface IAssertionCardListProps {
assertionResultList: IAssertionResult[];
onSelectSpan(spanId: string): void;
resultId: string;
testId: string;
}

const AssertionCardList: React.FC<IAssertionCardListProps> = ({
assertionResultList,
onSelectSpan,
testId,
resultId,
}) => {
const {open} = useCreateAssertionModal();
const [deleteAssertion] = useDeleteAssertionMutation();

const handleEdit = useCallback(
({assertion, spanListAssertionResult}: IAssertionResult) => {
const [{span}] = spanListAssertionResult;

open({
span,
testId,
assertion,
resultId,
});
},
[open, resultId, testId]
);

return (
<S.AssertionCardList>
{assertionResultList.length ? (
assertionResultList.map(assertionResult =>
assertionResult.spanListAssertionResult.length ? (
<AssertionCard
key={assertionResult.assertion?.assertionId}
assertionResult={assertionResult}
onSelectSpan={onSelectSpan}
onEdit={handleEdit}
onDelete={assertionId => deleteAssertion({testId, assertionId})}
/>
) : null
)
) : (
<S.EmptyStateContainer>
<S.EmptyStateIcon />
<Typography.Text disabled>No Data</Typography.Text>
</S.EmptyStateContainer>
)}
</S.AssertionCardList>
);
};

export default AssertionCardList;
2 changes: 2 additions & 0 deletions web/src/components/AssertionCardList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export {default} from './AssertionCardList';
43 changes: 43 additions & 0 deletions web/src/components/AssertionCheckRow/AssertionCheckRow.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {Badge, Typography} from 'antd';
import styled from 'styled-components';

export const AssertionCheckRow = styled.div`
display: grid;
grid-template-columns: 1.5fr 1fr .8fr 1fr 1fr;
gap: 14px;
cursor: pointer;
`;

export const Entry = styled.div`
display: flex;
flex-direction: column;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
`;

export const Label = styled(Typography.Text).attrs({
type: 'secondary',
})`
font-size: 12px;
`;

export const Value = styled(Typography.Text)`
font-size: 12px;
`;

export const LabelBadge = styled(Badge)`
> sup {
background-color: #f0f0f0;
color: black;
margin-right: 6px;
border-radius: 2px;
}
`;

export const SelectedLabelBadge = styled(LabelBadge)`
> sup {
color: #61175e;
border: 1px solid #61175e;
}
`;
62 changes: 62 additions & 0 deletions web/src/components/AssertionCheckRow/AssertionCheckRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {useMemo} from 'react';
import {difference} from 'lodash';
import OperatorService from '../../services/Operator.service';
import {ISpanAssertionResult} from '../../types/Assertion.types';
import {ISpan} from '../../types/Span.types';
import * as S from './AssertionCheckRow.styled';
import AttributeValue from '../AttributeValue';

interface IAssertionCheckRowProps {
result: ISpanAssertionResult;
span: ISpan;
assertionSelectorList: string[];
getIsSelectedSpan(spanId: string): boolean;
onSelectSpan(spanId: string): void;
}

const AssertionCheckRow: React.FC<IAssertionCheckRowProps> = ({
result: {propertyName, comparisonValue, operator, actualValue, hasPassed, spanId},
span: {signature},
assertionSelectorList,
getIsSelectedSpan,
onSelectSpan,
}) => {
const signatureSelectorList = signature.map(({value}) => value).concat([`#${spanId.slice(-4)}`]) || [];
const spanLabelList = difference(signatureSelectorList, assertionSelectorList);
const badgeList = useMemo(() => {
const isSelected = getIsSelectedSpan(spanId);

return (isSelected ? [<S.SelectedLabelBadge count="selected" key="selected" />] : []).concat(
spanLabelList
// eslint-disable-next-line react/no-array-index-key
.map((label, index) => <S.LabelBadge count={label} key={`${label}-${index}`} />)
);
}, [getIsSelectedSpan, spanId, spanLabelList]);

return (
<S.AssertionCheckRow onClick={() => onSelectSpan(spanId)}>
<S.Entry>
<S.Label>Span Labels</S.Label>
<S.Value>{badgeList}</S.Value>
</S.Entry>
<S.Entry>
<S.Label>Attribute</S.Label>
<S.Value>{propertyName}</S.Value>
</S.Entry>
<S.Entry>
<S.Label>Assertion Type</S.Label>
<S.Value>{OperatorService.getOperatorName(operator)}</S.Value>
</S.Entry>
<S.Entry>
<S.Label>Expected Value</S.Label>
<AttributeValue value={comparisonValue} />
</S.Entry>
<S.Entry>
<S.Label>Actual Value</S.Label>
<AttributeValue strong type={hasPassed ? 'success' : 'danger'} value={actualValue || '<Empty Value>'} />
</S.Entry>
</S.AssertionCheckRow>
);
};

export default AssertionCheckRow;
2 changes: 2 additions & 0 deletions web/src/components/AssertionCheckRow/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export {default} from './AssertionCheckRow';
Loading

0 comments on commit 1ebe755

Please sign in to comment.