Skip to content

Commit 9e4de71

Browse files
authored
Merge pull request #155 from cqse/ts/45910_project_and_baseline_selector_not_working
TS-45910: Azure DevOps extension: Dashboard widget only offers one project in dropdown
2 parents e0d57b9 + 2beea45 commit 9e4de71

7 files changed

Lines changed: 104 additions & 91 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ We use [semantic versioning](http://semver.org/):
44
- MINOR version when you add functionality in a backwards-compatible manner, and
55
- PATCH version when you make backwards compatible bug fixes.
66

7+
# 1.5.4
8+
- [fix] dashboard widget: project and baseline dropdowns only offered one element
9+
710
# 1.5.3
811
- [fix] work item integration: findings and test gap badges had outdated links to Teamscale
912
- [fix] work item integration: spec-item findings badges always showed an "unchanged" findings churn and clicking them led to an error page in Teamscale

package-lock.json

Lines changed: 33 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@
2626
"typescript": "^5.9.3"
2727
},
2828
"dependencies": {
29-
"@types/chosen-js": "^1.8.6",
30-
"chosen-js": "^1.8.7",
3129
"jquery": "^4.0.0",
3230
"jquery-ui-dist": "^1.13.3",
31+
"tom-select": "^2.6.0",
3332
"vss-web-extension-sdk": "^5.141.0"
3433
},
3534
"overrides": {

sites/widget-configuration.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
<script src="../lib/VSS.SDK.min.js"></script>
55
<script src="../lib/jquery.min.js"></script>
66
<script src="../lib/jquery-ui.min.js"></script>
7-
<script src="../lib/chosen.jquery.min.js"></script>
7+
<script src="../lib/tom-select.complete.min.js"></script>
88
<link rel="stylesheet" type="text/css" href="../styles/settings.css">
99
<link rel="stylesheet" type="text/css" href="../styles/jquery-ui.min.css">
10-
<link rel="stylesheet" type="text/css" href="../styles/chosen.min.css">
10+
<link rel="stylesheet" type="text/css" href="../styles/tom-select.default.min.css">
1111
</head>
1212
<body>
1313
<div class="container">

src/DashboardWidgetExtension/WidgetConfiguration.ts

Lines changed: 60 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import NotificationUtils from '../Utils/NotificationUtils';
1414
import {ExtensionSetting} from "../Settings/ExtensionSetting";
1515
import UiUtils = require('../Utils/UiUtils');
1616

17+
declare const TomSelect: any;
18+
1719
export class Configuration {
1820
private projectSettings: ProjectSettings = null;
1921
private organizationSettings: Settings = null;
@@ -22,18 +24,19 @@ export class Configuration {
2224

2325
private teamscaleClient: TeamscaleClient = null;
2426
private tgaTeamscaleClient: TeamscaleClient = null;
25-
private teamscaleTgaProjectSelect = document.getElementById('teamscale-tga-project-select') as HTMLSelectElement;
2627

2728
private notificationUtils: NotificationUtils = null;
2829
private emailContact: string = '';
2930

30-
private teamscaleProjectSelect = document.getElementById('teamscale-project-select') as HTMLSelectElement;
31-
private teamscaleBaselineSelect = document.getElementById('ts-baseline-select') as HTMLSelectElement;
3231
private baselineDaysInput = document.getElementById('baseline-days-input') as HTMLInputElement;
3332
private datepicker = $('#datepicker');
3433
private testGapCheckbox = $('#show-test-gap');
3534
private separateTgaServerCheckbox = $('#separate-tga-server');
3635

36+
private tsProjectSelect: any;
37+
private tsTgaProjectSelect: any;
38+
private tsBaselineSelect: any;
39+
3740
private widgetHelpers: any;
3841
private readonly notificationService: any;
3942
private readonly controlService: any;
@@ -63,10 +66,15 @@ export class Configuration {
6366
this.separateTgaServerCheckbox.prop('checked', this.widgetSettings.useSeparateTgaServer);
6467
}
6568
this.zipTgaConfiguration();
66-
$('#teamscale-project-select').chosen({width: '100%'}).on('change',
67-
() => this.fillDropdownWithTeamscaleBaselines(notifyWidgetChange));
68-
$('#teamscale-tga-project-select').chosen({width: '100%'}).on('change', notifyWidgetChange);
69-
$('#ts-baseline-select').chosen({width: '95%'}).on('change', notifyWidgetChange);
69+
70+
this.tsProjectSelect = new TomSelect('#teamscale-project-select', {});
71+
this.tsProjectSelect.on('change', () => this.fillDropdownWithTeamscaleBaselines(notifyWidgetChange));
72+
73+
this.tsTgaProjectSelect = new TomSelect('#teamscale-tga-project-select', {});
74+
this.tsTgaProjectSelect.on('change', notifyWidgetChange);
75+
76+
this.tsBaselineSelect = new TomSelect('#ts-baseline-select', {});
77+
this.tsBaselineSelect.on('change', notifyWidgetChange);
7078

7179
this.loadAndCheckConfiguration().then(() => this.fillDropdownsWithProjects())
7280
.then(() => this.fillDropdownWithTeamscaleBaselines(notifyWidgetChange))
@@ -118,7 +126,7 @@ export class Configuration {
118126
if (this.separateTgaServerCheckbox.is(':checked')) {
119127
displayAttribute = 'block';
120128

121-
if (!this.teamscaleTgaProjectSelect.firstChild) {
129+
if (Object.keys(this.tsTgaProjectSelect.options).length === 0) {
122130
this.fillTgaDropdownWithProjects();
123131
}
124132
}
@@ -129,12 +137,12 @@ export class Configuration {
129137
}
130138

131139
private handleMissingTgaServerConfig() {
132-
const element = document.createElement('option');
133-
element.textContent = 'Error: No TGA server configured. Deactivate separate Server option or' +
134-
' configure TGA Server.';
135-
this.teamscaleTgaProjectSelect.appendChild(element);
136-
$('#' + this.teamscaleTgaProjectSelect.id).prop('disabled', true);
137-
$('#' + this.teamscaleTgaProjectSelect.id).trigger('chosen:updated');
140+
const message = 'Error: No TGA server configured. Deactivate separate Server option or configure TGA Server.';
141+
this.tsTgaProjectSelect.clear(true);
142+
this.tsTgaProjectSelect.clearOptions();
143+
this.tsTgaProjectSelect.addOption({value: message, text: message});
144+
this.tsTgaProjectSelect.setValue(message, true);
145+
this.tsTgaProjectSelect.disable();
138146
return Promise.resolve();
139147
}
140148

@@ -149,7 +157,7 @@ export class Configuration {
149157
* Loads a list of accessible projects from the Teamscale server and appends them to the TQE dropdown menu.
150158
*/
151159
private async fillTqeDropdownWithProjects() {
152-
return this.fillDropdownWithProjects(this.teamscaleClient, this.teamscaleProjectSelect, '#teamscale-project-select');
160+
return this.fillDropdownWithProjects(this.teamscaleClient, this.tsProjectSelect, 'teamscaleProject');
153161
}
154162

155163
/**
@@ -164,13 +172,13 @@ export class Configuration {
164172
}
165173
this.tgaTeamscaleClient = new TeamscaleClient(tgaUrl);
166174
}
167-
return this.fillDropdownWithProjects(this.tgaTeamscaleClient, this.teamscaleTgaProjectSelect, '#teamscale-tga-project-select');
175+
return this.fillDropdownWithProjects(this.tgaTeamscaleClient, this.tsTgaProjectSelect, 'tgaTeamscaleProject');
168176
}
169177

170178
/**
171179
* Loads a list of accessible projects from the Teamscale server and appends them to the dropdown menu.
172180
*/
173-
private async fillDropdownWithProjects(teamscaleClient: TeamscaleClient, selectElement: HTMLSelectElement, selectElementId: string) {
181+
private async fillDropdownWithProjects(teamscaleClient: TeamscaleClient, projectSelect: any, settingsKey: string) {
174182
let projects: string[];
175183
try {
176184
projects = await teamscaleClient.retrieveTeamscaleProjects();
@@ -179,26 +187,31 @@ export class Configuration {
179187
return Promise.reject(error);
180188
}
181189

190+
projectSelect.clear(true);
191+
projectSelect.clearOptions();
192+
projectSelect.enable();
182193
for (const project of projects) {
183-
const element = document.createElement('option');
184-
element.textContent = project;
185-
element.value = project;
186-
if (this.widgetSettings) {
187-
element.selected = this.widgetSettings.teamscaleProject === project;
188-
}
189-
selectElement.appendChild(element);
194+
projectSelect.addOption({value: project, text: project});
190195
}
191196

192-
$(selectElementId).trigger('chosen:updated');
197+
const savedValue = this.widgetSettings && this.widgetSettings[settingsKey];
198+
if (savedValue && projects.indexOf(savedValue) !== -1) {
199+
projectSelect.setValue(savedValue, true);
200+
} else if (projects.length > 0) {
201+
projectSelect.setValue(projects[0], true);
202+
}
193203
}
194204

195205
/**
196206
* Loads the list configured baselines for a project from the Teamscale server and appends them to the dropdown menu.
197207
*/
198208
private async fillDropdownWithTeamscaleBaselines(notifyWidgetChange) {
199209
// use input value and not widgetSetting Object which might hold an outdated project name
200-
// since the chosen change event of the project selector is fired before the settings object update
201-
const teamscaleProject: string = this.teamscaleProjectSelect.value;
210+
// since the change event of the project selector is fired before the settings object update
211+
const teamscaleProject: string = this.tsProjectSelect.getValue();
212+
if (!teamscaleProject) {
213+
return;
214+
}
202215

203216
let baselines: ITeamscaleBaseline[];
204217
try {
@@ -209,43 +222,31 @@ export class Configuration {
209222
return Promise.reject(error);
210223
}
211224

212-
while (this.teamscaleBaselineSelect.firstChild) {
213-
this.teamscaleBaselineSelect.removeChild(this.teamscaleBaselineSelect.firstChild);
214-
}
215-
216-
this.disableBaselineDropdownForProjectsWithoutBaselines(baselines, teamscaleProject);
225+
this.tsBaselineSelect.clear(true);
226+
this.tsBaselineSelect.clearOptions();
217227

218-
for (const baseline of baselines) {
219-
const element = document.createElement('option');
220-
const date = new Date(baseline.timestamp);
221-
element.textContent = baseline.name + ' (' + date.toLocaleDateString() + ')';
222-
element.value = baseline.name;
223-
if (this.widgetSettings) {
224-
element.selected = this.widgetSettings.tsBaseline === baseline.name;
228+
if (baselines.length === 0) {
229+
const message = 'No baseline configured for project »' + teamscaleProject + '«';
230+
this.tsBaselineSelect.addOption({value: message, text: message});
231+
this.tsBaselineSelect.setValue(message, true);
232+
this.tsBaselineSelect.disable();
233+
} else {
234+
this.tsBaselineSelect.enable();
235+
for (const baseline of baselines) {
236+
const date = new Date(baseline.timestamp);
237+
const text = baseline.name + ' (' + date.toLocaleDateString() + ')';
238+
this.tsBaselineSelect.addOption({value: baseline.name, text: text});
239+
}
240+
if (this.widgetSettings && this.widgetSettings.tsBaseline) {
241+
this.tsBaselineSelect.setValue(this.widgetSettings.tsBaseline, true);
225242
}
226-
this.teamscaleBaselineSelect.appendChild(element);
227243
}
228244

229-
// update widget settings to get rid of a baseline which belongs to the formally chosen project
245+
// update widget settings to get rid of a baseline which belongs to the formerly chosen project
230246
this.getAndUpdateCustomSettings();
231-
$('#ts-baseline-select').trigger('chosen:updated');
232247
notifyWidgetChange();
233248
}
234249

235-
/**
236-
* Disables the baseline chooser for projects without configured Teamscale baselines.
237-
*/
238-
private disableBaselineDropdownForProjectsWithoutBaselines(baselines: ITeamscaleBaseline[], teamscaleProject: string) {
239-
if (baselines.length === 0) {
240-
const element = document.createElement('option');
241-
element.textContent = 'No baseline configured for project »' + teamscaleProject + '«';
242-
this.teamscaleBaselineSelect.appendChild(element);
243-
$('#ts-baseline-select').prop('disabled', true);
244-
} else {
245-
$('#ts-baseline-select').prop('disabled', false);
246-
}
247-
}
248-
249250
/**
250251
* Loads the Teamscale email contact from the organization settings and assures that a Teamscale server url and project
251252
* name is set in the Azure DevOps project settings.
@@ -304,14 +305,14 @@ export class Configuration {
304305
* Read the current configuration as specified in the configuration form. Stores it as class member and returns it.
305306
*/
306307
private getAndUpdateCustomSettings(): ITeamscaleWidgetSettings {
307-
const teamscaleProject: string = this.teamscaleProjectSelect.value;
308-
const tgaTeamscaleProject: string = this.teamscaleTgaProjectSelect.value;
308+
const teamscaleProject: string = this.tsProjectSelect ? this.tsProjectSelect.getValue() : '';
309+
const tgaTeamscaleProject: string = this.tsTgaProjectSelect ? this.tsTgaProjectSelect.getValue() : '';
309310
const baselineDays: number = Number(this.baselineDaysInput.value);
310311
let startFixedDate: number;
311312
if (this.datepicker.datepicker('getDate')) {
312313
startFixedDate = this.datepicker.datepicker('getDate').getTime();
313314
}
314-
const tsBaseline: string = this.teamscaleBaselineSelect.value;
315+
const tsBaseline: string = this.tsBaselineSelect ? this.tsBaselineSelect.getValue() : '';
315316
const showTestGapBadge: boolean = document.getElementById('show-test-gap').checked;
316317
const useSeparateTgaServer: boolean = document.getElementById('separate-tga-server').checked;
317318

tsconfig.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
"rootDir": "src/",
1010
"outDir": "dist/",
1111
"types": [
12-
"vss-web-extension-sdk",
13-
"chosen-js"
12+
"vss-web-extension-sdk"
1413
],
1514
"lib": [ "es2015", "dom" ]
1615
}

vss-extension.json

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,14 @@
168168
"packagePath": "lib/VSS.SDK.min.js"
169169
},
170170
{
171-
"path": "node_modules/chosen-js/chosen.jquery.min.js",
171+
"path": "node_modules/tom-select/dist/js/tom-select.complete.min.js",
172172
"addressable": true,
173-
"packagePath": "lib/chosen.jquery.min.js"
173+
"packagePath": "lib/tom-select.complete.min.js"
174174
},
175175
{
176-
"path": "node_modules/chosen-js/chosen.min.css",
176+
"path": "node_modules/tom-select/dist/css/tom-select.default.min.css",
177177
"addressable": true,
178-
"packagePath": "styles/chosen.min.css"
178+
"packagePath": "styles/tom-select.default.min.css"
179179
},
180180
{
181181
"path": "node_modules/jquery/dist/jquery.min.js",
@@ -197,11 +197,6 @@
197197
"addressable": true,
198198
"packagePath": "styles/images/ui-icons_444444_256x240.png"
199199
},
200-
{
201-
"path": "node_modules/chosen-js/chosen-sprite.png",
202-
"addressable": true,
203-
"packagePath": "styles/chosen-sprite.png"
204-
},
205200
{
206201
"path": "scripts/require.js",
207202
"addressable": true

0 commit comments

Comments
 (0)