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

x1032 Add user full name to Release Recipient #393

Merged
merged 4 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 76 additions & 4 deletions cypress/e2e/pages/configuration.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { AddReleaseRecipientMutation, AddReleaseRecipientMutationVariables } from '../../../src/types/sdk';
import {
AddReleaseRecipientMutation,
AddReleaseRecipientMutationVariables,
UpdateReleaseRecipientFullNameMutation,
UpdateReleaseRecipientFullNameMutationVariables
} from '../../../src/types/sdk';
import { selectOption } from '../shared/customReactSelect.cy';

describe('Configuration Spec', () => {
Expand Down Expand Up @@ -116,6 +121,39 @@ describe('Configuration Spec', () => {
});
});

describe('displays extra input field when specified', () => {
const config = {
name: 'Release Recipients',
tabName: 'Release Recipients',
field: 'cs41',
extraFieldValue: 'Csaba Csordas',
buttonName: '+ Add Username',
newValue: 'az99',
newExtraFieldValue: 'Arielle Zimran'
};
context('configuration table should contains extra column to display extra field values', () => {
before(() => {
cy.scrollTo(0, 0);
cy.findByText(config.name).click();
});

it('displays extra field values', () => {
cy.get(`div[data-testid="config"]:contains(${config.name})`)
.find(`tr:contains(${config.field}) `)
.find('input')
.first()
.should('have.value', config.extraFieldValue);
});

it('displays extra field input when adding new entity', () => {
clickButton(config.buttonName);
cy.findByTestId('input-field').scrollIntoView().focus().type(`${config.newValue}`);
enterNewExtraFieldValue(config.extraFieldValue);
cy.findByText('Saved').scrollIntoView().should('be.visible');
});
});
});

context('When adding a Release Recipients fails', () => {
before(() => {
cy.msw().then(({ worker, graphql }) => {
Expand All @@ -134,8 +172,6 @@ describe('Configuration Spec', () => {
)
);
});
cy.scrollTo(0, 0);
cy.findByText('Release Recipients').click();
});

it('shows an error message', () => {
Expand All @@ -147,8 +183,40 @@ describe('Configuration Spec', () => {
});
});
});

context('When Updating a Release Recipient full name successfully', () => {
before(() => {
cy.msw().then(({ worker, graphql }) => {
worker.use(
graphql.mutation<UpdateReleaseRecipientFullNameMutation, UpdateReleaseRecipientFullNameMutationVariables>(
'UpdateReleaseRecipientFullName',
(req, res, ctx) => {
return res.once(
ctx.data({
updateReleaseRecipientFullName: {
username: 'et2',
fullName: 'Ethan Twin',
enabled: true
}
})
);
}
)
);
});
cy.get(`div[data-testid="config"]:contains('Release Recipients')`)
.find(`tr:contains('et2') `)
.find('input')
.first()
.focus()
.type(`Ethan Twin {enter}`, { force: true });
});
it('shows a success message', () => {
cy.findByText('Changes for "et2" saved').should('be.visible');
});
});
function selectElement(findTag: string) {
return cy.get(findTag).scrollIntoView().click({
return cy.get(findTag).last().scrollIntoView().click({
force: true
});
}
Expand All @@ -158,4 +226,8 @@ describe('Configuration Spec', () => {
function enterNewValue(value: string) {
cy.findByTestId('input-field').scrollIntoView().focus().type(`${value}{enter}`, { force: true });
}

function enterNewExtraFieldValue(value: string) {
cy.findByTestId('extra-input-field').scrollIntoView().focus().type(`${value}{enter}`, { force: true });
}
});
5 changes: 3 additions & 2 deletions src/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface TableHeaderProps {
sortProps?: SortProps;
children?: ReactNode | ReactNode[];
allCapital?: boolean;
colSpan?: number;
}
/**
* @example
Expand Down Expand Up @@ -58,9 +59,9 @@ export const TableHead = ({ children, fixed = false }: TableHeadProps) => {
return <thead className={`${fixed ? 'sticky top-0' : ''}`}>{children}</thead>;
};

export const TableHeader = ({ children, sortProps, allCapital = true, ...rest }: TableHeaderProps) => {
export const TableHeader = ({ children, sortProps, allCapital = true, colSpan, ...rest }: TableHeaderProps) => {
return (
<th className="px-6 py-3 bg-gray-50 text-left select-none" {...rest}>
<th className="px-6 py-3 bg-gray-50 text-left select-none" colSpan={colSpan} {...rest}>
<>
{
<IconButton
Expand Down
147 changes: 116 additions & 31 deletions src/components/entityManager/EntityManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Input } from '../forms/Input';
import BlueButton from '../buttons/BlueButton';
import { useMachine } from '@xstate/react';
import Success from '../notifications/Success';
import Warning from '../notifications/Warning';
import { capitalize } from 'lodash';
import WhiteButton from '../buttons/WhiteButton';
import PinkButton from '../buttons/PinkButton';
Expand All @@ -14,6 +13,7 @@ import { BooleanEntityRow } from './BooleanEntityRow';
import { createEntityManagerMachine } from './entityManager.machine';
import { alphaNumericSortDefault } from '../../types/stan';
import CustomReactSelect, { OptionType } from '../forms/CustomReactSelect';
import Warning from '../notifications/Warning';

export type EntityValueType = boolean | string | number;

Expand All @@ -23,6 +23,14 @@ type ValueFieldComponentInfo = {
valueOptions?: string[];
};

type ExtraEntityColumn<E> = {
label: string;
value: string;
keyFieldPlaceholder: string;
extraFieldPlaceholder: string;
onChange(value: string, extraValue?: string): Promise<E>;
};

type EntityManagerProps<E> = {
/**
* The initial entities to display in the table
Expand Down Expand Up @@ -55,7 +63,7 @@ type EntityManagerProps<E> = {
* Callback when a new entity is to be created
* @param value the value of the new entity
*/
onCreate(value: string): Promise<E>;
onCreate(value: string, extraValue?: string): Promise<E>;

/**
* Callback when value changes
Expand All @@ -66,8 +74,12 @@ type EntityManagerProps<E> = {
* Display key field as a dropdown
*/
displayKeyFieldAsDropDown?: boolean;
};

/**
* Extra property of the entity to display in the table
*/
extraDisplayColumnName?: ExtraEntityColumn<E>;
};
export default function EntityManager<E extends Record<string, EntityValueType>>({
initialEntities,
displayKeyColumnName,
Expand All @@ -76,22 +88,27 @@ export default function EntityManager<E extends Record<string, EntityValueType>>
valueFieldComponentInfo,
onCreate,
onChangeValue,
displayKeyFieldAsDropDown = false
displayKeyFieldAsDropDown = false,
extraDisplayColumnName = undefined
}: EntityManagerProps<E>) {
const entityManagerMachine = React.useMemo(() => {
return createEntityManagerMachine<E>(initialEntities, displayKeyColumnName, valueColumnName).withConfig({
services: {
createEntity: (ctx, e) => {
if (e.type !== 'CREATE_NEW_ENTITY') return Promise.reject();
return onCreate(e.value);
return onCreate(e.value, e.extraValue);
},
valueChanged: (context, e) => {
if (e.type !== 'VALUE_CHANGE') return Promise.reject();
return onChangeValue(e.entity, e.value);
},
updateExtraProperty: (context, e) => {
if (e.type !== 'EXTRA_PROPERTY_UPDATE_VALUE' || !extraDisplayColumnName) return Promise.reject();
return extraDisplayColumnName.onChange(e.value, e.extraValue);
}
}
});
}, [initialEntities, displayKeyColumnName, valueColumnName, onChangeValue, onCreate]);
}, [initialEntities, displayKeyColumnName, valueColumnName, onChangeValue, onCreate, extraDisplayColumnName]);

const [current, send] = useMachine(entityManagerMachine);

Expand All @@ -118,6 +135,7 @@ export default function EntityManager<E extends Record<string, EntityValueType>>
* Will receive focus when it appears on screen.
*/
const inputRef = useRef<HTMLInputElement>(null);
const extraInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (current.matches('draftCreation')) {
inputRef.current?.select();
Expand All @@ -132,9 +150,13 @@ export default function EntityManager<E extends Record<string, EntityValueType>>
if (value === '') {
return;
}
send({ type: 'CREATE_NEW_ENTITY', value });
let extraValue: string | undefined;
if (extraDisplayColumnName) {
extraValue = extraInputRef.current?.value.trim();
}
send({ type: 'CREATE_NEW_ENTITY', value, extraValue });
setDraftValue('');
}, [draftValue, setDraftValue, send]);
}, [draftValue, setDraftValue, send, extraDisplayColumnName]);

/**
* Callback handler for when an EntityRow changes (i.e. enabled property is toggled)
Expand Down Expand Up @@ -171,6 +193,28 @@ export default function EntityManager<E extends Record<string, EntityValueType>>
*/
const handleOnCancel = () => send({ type: 'DISCARD_DRAFT' });

const handleExtraValueUpdate = useCallback(
(keyValue: string, extraValue: string) => {
send({
type: 'EXTRA_PROPERTY_UPDATE_VALUE',
value: keyValue,
extraValue: extraValue
});
},
[send]
);

const handleOnChangeForExtraDisplayColumn = useCallback(
(entity: E, extraValue: string) => {
if (!extraDisplayColumnName) return;
send({
type: 'EXTRA_PROPERTY_DRAFT_VALUE',
entity: { ...entity, [extraDisplayColumnName.value]: extraValue }
});
},
[send, extraDisplayColumnName]
);

const getValueFieldComponent = (
valueFieldComponentInfo: ValueFieldComponentInfo,
entity: E | undefined,
Expand Down Expand Up @@ -204,6 +248,47 @@ export default function EntityManager<E extends Record<string, EntityValueType>>
return <></>;
}
};
const setInputFieldsRow = () => {
return (
<tr>
<TableCell colSpan={2}>
<Input
type="text"
placeholder={extraDisplayColumnName ? extraDisplayColumnName.keyFieldPlaceholder : ''}
ref={inputRef}
data-testid="input-field"
disabled={isCreatingEntity}
onChange={handleOnInputChange}
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleOnSave();
} else if (e.key === 'Escape') {
handleOnCancel();
}
}}
/>
</TableCell>
{extraDisplayColumnName && (
<TableCell colSpan={2}>
<Input
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have the class style for the full name to be as no border (or a very subtle one) as in normal condition and when we start editing (that means when it gets focus), the border can appear? This way the field will look more similar to other, I think

type="text"
placeholder={extraDisplayColumnName.extraFieldPlaceholder}
ref={extraInputRef}
data-testid="extra-input-field"
disabled={isCreatingEntity}
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleOnSave();
} else if (e.key === 'Escape') {
handleOnCancel();
}
}}
/>
</TableCell>
)}
</tr>
);
};

return (
<div className="space-y-4">
Expand All @@ -212,8 +297,9 @@ export default function EntityManager<E extends Record<string, EntityValueType>>
<Table>
<TableHead>
<tr>
<TableHeader>{displayKeyColumnName}</TableHeader>
<TableHeader>{valueColumnName}</TableHeader>
<TableHeader colSpan={2}>{displayKeyColumnName}</TableHeader>
{extraDisplayColumnName && <TableHeader colSpan={2}>{extraDisplayColumnName.label}</TableHeader>}
<TableHeader colSpan={2}>{valueColumnName}</TableHeader>
</tr>
</TableHead>
<TableBody>
Expand Down Expand Up @@ -241,7 +327,25 @@ export default function EntityManager<E extends Record<string, EntityValueType>>
) : (
orderedEntities.map((entity, indx) => (
<tr key={indx}>
<TableCell>{entity[displayKeyColumnName]}</TableCell>
<TableCell colSpan={2}>{entity[displayKeyColumnName]}</TableCell>
{extraDisplayColumnName && (
<TableCell colSpan={2}>
<Input
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to switch it back to a label or input style resembling a label ,once user finishes editing or on blur?

type="text"
placeholder="Enter user full name"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is generic class, so I think you should avoid any specifics here. For e.g, if we need to add an extra column in another config tab, this won't make sense

onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleExtraValueUpdate(String(entity[displayKeyColumnName]), e.currentTarget.value);
e.currentTarget.blur();
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
handleOnChangeForExtraDisplayColumn(entity, e.target.value);
}}
value={String(entity[extraDisplayColumnName.value])}
/>
</TableCell>
)}
{getValueFieldComponent(
valueFieldComponentInfo,
entity,
Expand All @@ -251,26 +355,7 @@ export default function EntityManager<E extends Record<string, EntityValueType>>
</tr>
))
)}
{showDraft && (
<tr>
<TableCell colSpan={2}>
<Input
ref={inputRef}
data-testid={'input-field'}
type="text"
disabled={isCreatingEntity}
onChange={handleOnInputChange}
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleOnSave();
} else if (e.key === 'Escape') {
handleOnCancel();
}
}}
/>
</TableCell>
</tr>
)}
{showDraft && setInputFieldsRow()}
</TableBody>
</Table>
<div className="flex flex-row justify-end items-center space-x-3">
Expand Down
Loading
Loading