Skip to content

Commit aea4508

Browse files
feat: update jira config page to be more functional [INTEG-2672] (#9853)
* feat: update jira config page to be more functional * fix: add attributes to make sure the tests pass and can find autocomplete component * feat: abstract out styles into the scss file
1 parent fd10534 commit aea4508

File tree

5 files changed

+95
-84
lines changed

5 files changed

+95
-84
lines changed

apps/jira/jira-app/src/components/App/Steps/ContentTypeStep.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ const ContentTypeStep = ({
6161
);
6262
} else {
6363
contentToRender = (
64-
<Flex>
64+
<Flex flexDirection="column" gap="spacingM">
6565
{sortedContentTypes.map((ct) => (
66-
<FormControl id={ct.name} key={ct.id}>
66+
<FormControl id={ct.name} key={ct.id} marginBottom="none">
6767
<Checkbox
6868
testId={`ct-item-${ct.id}`}
6969
name={ct.name}

apps/jira/jira-app/src/components/App/Steps/InstanceStep.tsx

Lines changed: 49 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,7 @@
11
import React from 'react';
2-
import { Heading, Paragraph, Select, Option, TextInput, Card } from '@contentful/f36-components';
2+
import { Heading, Paragraph, Select, Option, Autocomplete } from '@contentful/f36-components';
33
import { JiraCloudResource, CloudProject } from '../../../interfaces';
44

5-
// using lodash.debouce basically breaks test with infinite timers
6-
const debounce = function (fn: Function, timeout: number): Function {
7-
let timer: any;
8-
9-
return function (...args: any[]) {
10-
clearTimeout(timer);
11-
timer = setTimeout(() => {
12-
fn(...args);
13-
}, timeout);
14-
};
15-
};
16-
17-
interface State {
18-
inputValue: string;
19-
}
20-
215
interface Props {
226
resources: JiraCloudResource[];
237
selectedResource: string;
@@ -28,30 +12,23 @@ interface Props {
2812
queryProjects: (query: string) => void;
2913
}
3014

31-
export default class InstanceStep extends React.Component<Props, State> {
32-
constructor(props: Props) {
33-
super(props);
34-
35-
this.state = {
36-
inputValue: '',
37-
};
15+
export default class InstanceStep extends React.Component<Props> {
16+
componentDidMount() {
17+
this.setInputTestId();
3818
}
3919

40-
handleInputChange = debounce((ev: any) => {
41-
this.setState({
42-
inputValue: ev.target.value,
43-
});
44-
45-
this.props.queryProjects(ev.target.value);
46-
}, 300);
47-
48-
selectProject = (project: CloudProject) => {
49-
this.setState({
50-
inputValue: project.name,
51-
});
20+
componentDidUpdate() {
21+
this.setInputTestId();
22+
}
5223

53-
this.props.pickProject(project);
54-
};
24+
setInputTestId() {
25+
// Forma 36 Autocomplete renders an input inside the component
26+
// We find it and set the data-test-id for the test
27+
const el = document.querySelector('.project-autocomplete input');
28+
if (el) {
29+
el.setAttribute('data-test-id', 'cf-ui-text-input');
30+
}
31+
}
5532

5633
render() {
5734
const { resources, pickResource, selectedResource, projects, selectedProject } = this.props;
@@ -61,51 +38,45 @@ export default class InstanceStep extends React.Component<Props, State> {
6138
<Heading>Configure</Heading>
6239
<Paragraph>Select the Jira site and project you want to connect</Paragraph>
6340
<div className="jira-config" data-test-id="instance-step">
64-
<Select
65-
testId="instance-selector"
66-
className="selector"
67-
// @ts-ignore: 2339
68-
onChange={(e) => pickResource(e.target.value)}
69-
isDisabled={resources.length === 1}
70-
value={selectedResource || ''}>
71-
<Option value="">Select a site</Option>
72-
{resources.map((r) => (
73-
<Option key={r.id} value={r.id}>
74-
{r.url.replace('https://', '')}
75-
</Option>
76-
))}
77-
</Select>
41+
<div className="jira-config-row">
42+
<Select
43+
testId="instance-selector"
44+
className="selector"
45+
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => pickResource(e.target.value)}
46+
isDisabled={resources.length === 1}
47+
value={selectedResource || ''}>
48+
<Option value="">Select a site</Option>
49+
{resources.map((r) => (
50+
<Option key={r.id} value={r.id}>
51+
{r.url.replace('https://', '')}
52+
</Option>
53+
))}
54+
</Select>
7855

79-
<div className="search-projects">
80-
<TextInput
81-
width="full"
82-
placeholder={selectedProject ? selectedProject.name : 'Search for a project'}
83-
value={this.state.inputValue}
84-
onChange={(ev) => {
85-
ev.persist();
86-
this.handleInputChange(ev);
87-
}}
88-
onFocus={() => {
89-
this.setState({ inputValue: '' });
56+
<Autocomplete<CloudProject>
57+
items={projects}
58+
itemToString={(item) => (item ? item.name : '')}
59+
testId="project-autocomplete"
60+
noMatchesMessage="No projects found"
61+
onSelectItem={(item) => {
62+
if (item) this.props.pickProject(item);
9063
}}
91-
onBlur={() => {
92-
this.setState({ inputValue: selectedProject ? selectedProject.name : '' });
64+
selectedItem={selectedProject || undefined}
65+
onInputValueChange={(inputValue) => {
66+
this.props.queryProjects(inputValue || '');
9367
}}
94-
/>
95-
<div className="search-projects-results">
96-
{projects.map((project) => {
68+
renderItem={(item: CloudProject, inputValue: string) => {
69+
const isSelected = selectedProject && selectedProject.id === item.id;
9770
return (
98-
<Card
99-
key={project.id}
100-
testId="search-result-project"
101-
onClick={() => {
102-
this.selectProject(project);
103-
}}>
104-
{project.name}
105-
</Card>
71+
<div
72+
data-test-id="search-result-project"
73+
className={`autocomplete-item${isSelected ? ' selected' : ''}`}>
74+
{item.name}
75+
</div>
10676
);
107-
})}
108-
</div>
77+
}}
78+
className="project-autocomplete"
79+
/>
10980
</div>
11081
</div>
11182
</>

apps/jira/jira-app/src/components/App/Steps/JiraStep.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const JiraStep = () => (
1111
</Paragraph>
1212
<Paragraph>
1313
<Button
14+
isFullWidth
1415
onClick={() => window.open('https://marketplace.atlassian.com/apps/1221865/', '_blank')}>
1516
Get companion app for Jira Cloud
1617
</Button>

apps/jira/jira-app/src/index.scss

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,45 @@ header img {
234234

235235
}
236236
}
237+
238+
.project-autocomplete {
239+
&, input, [role='listbox'] {
240+
width: 100%;
241+
min-width: 320px;
242+
max-width: 400px;
243+
}
244+
[role='listbox'] {
245+
max-height: 600px;
246+
}
247+
}
248+
249+
.jira-config-row {
250+
display: flex;
251+
gap: 16px;
252+
align-items: center;
253+
width: 100%;
254+
}
255+
256+
.selector {
257+
min-width: 320px;
258+
max-width: 400px;
259+
flex: 1;
260+
}
261+
262+
.project-autocomplete {
263+
min-width: 320px;
264+
max-width: 400px;
265+
flex: 1;
266+
}
267+
268+
.autocomplete-item {
269+
background: white;
270+
padding: 10px 16px;
271+
cursor: pointer;
272+
font-weight: normal;
273+
}
274+
275+
.autocomplete-item.selected {
276+
background: #f3f4f6;
277+
font-weight: bold;
278+
}

apps/jira/jira-app/src/index.spec.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ describe('The Jira App Components', () => {
255255
);
256256

257257
// expect project data to not have loaded yet in the next <input>
258-
expect(projectSearchInput.placeholder).toEqual('Search for a project');
258+
expect(projectSearchInput.placeholder).toEqual('Search');
259259

260260
// pick an instance
261261
fireEvent.change(instanceSelector, {
@@ -277,9 +277,6 @@ describe('The Jira App Components', () => {
277277

278278
fireEvent.click(wrapper.getByTestId('search-result-project'));
279279

280-
// expect project data to have loaded into the project <input>'s placeholder
281-
expect(projectSearchInput.placeholder).toEqual('Project name 2');
282-
283280
const contentTypeList = wrapper.getByTestId('content-types');
284281

285282
// expect all content types to show up in the markup

0 commit comments

Comments
 (0)