Skip to content

Commit

Permalink
Merge pull request #60 from keyoke/keyoke/features
Browse files Browse the repository at this point in the history
Keyoke/features
  • Loading branch information
keyoke authored Oct 27, 2021
2 parents 0c452dd + 8d01ea5 commit 50bb8d3
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 14 deletions.
85 changes: 78 additions & 7 deletions src/block-duplicate-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ class duplicateObserver implements IWorkItemNotificationListener {
)) as string;

const similarityIndex: number = await this.getSimilarityIndex();
const includeTitle: boolean = await this.getIncludeTitle();
const includeDesciption: boolean = await this.getIncludeDesciption();

if (id) {
this._logger.debug(`System.Id is '${id}'.`);
Expand All @@ -133,6 +135,8 @@ class duplicateObserver implements IWorkItemNotificationListener {
this._logger.debug(`System.Description is '${description}'.`);
this._logger.debug(`System.WorkItemType is '${type}'.`);
this._logger.debug(`similarityIndex is '${similarityIndex}'.`);
this._logger.debug(`includeTitle is '${includeTitle}'`);
this._logger.debug(`includeDesciption is '${includeDesciption}'`);

const wiqlQuery = `SELECT [System.Id] FROM WorkItems WHERE [System.WorkItemType] = '${type}' AND [State] <> 'Closed' ORDER BY [System.CreatedDate] DESC`;
this._logger.debug(`WIQL Query is '${wiqlQuery}'.`);
Expand Down Expand Up @@ -167,6 +171,8 @@ class duplicateObserver implements IWorkItemNotificationListener {
this.normalizeString(title),
this.normalizeString(description),
similarityIndex,
includeTitle,
includeDesciption,
chunk_items
)
);
Expand All @@ -193,12 +199,28 @@ class duplicateObserver implements IWorkItemNotificationListener {

// did we find any duplicates?
if (duplicate) {
this._logger.info(
'A duplicate work item of the same type exists with similar title and description.'
);
this._workItemFormService.setError(
'A duplicate work item of the same type exists with similar title and description.'
);
if (includeTitle && includeDesciption) {
this._logger.info(
'A duplicate work item of the same type exists with similar title and description.'
);
this._workItemFormService.setError(
'A duplicate work item of the same type exists with similar title and description.'
);
} else if (includeTitle) {
this._logger.info(
'A duplicate work item of the same type exists with similar title.'
);
this._workItemFormService.setError(
'A duplicate work item of the same type exists with similar title.'
);
} else {
this._logger.info(
'A duplicate work item of the same type exists with similar description.'
);
this._workItemFormService.setError(
'A duplicate work item of the same type exists with similar description.'
);
}
} else {
this._logger.info('Not a Duplicate Work item.');
this._workItemFormService.clearError();
Expand Down Expand Up @@ -246,6 +268,44 @@ class duplicateObserver implements IWorkItemNotificationListener {
return similarityIndex;
}

// Get stored index or return default
private async getIncludeTitle(): Promise<boolean> {
const dataManager: IExtensionDataManager =
await this._dataService.getExtensionDataManager(
SDK.getExtensionContext().id,
await SDK.getAccessToken()
);

// Get current value for setting
const includeTitle: boolean = await dataManager.getValue<boolean>(
'IncludeTitle',
{
scopeType: 'Default',
}
);

return includeTitle;
}

// Get stored index or return default
private async getIncludeDesciption(): Promise<boolean> {
const dataManager: IExtensionDataManager =
await this._dataService.getExtensionDataManager(
SDK.getExtensionContext().id,
await SDK.getAccessToken()
);

// Get current value for setting
const includeDesciption: boolean = await dataManager.getValue<boolean>(
'IncludeDesciption',
{
scopeType: 'Default',
}
);

return includeDesciption;
}

// perform similarity logic on a batch of WI's
private async validateWorkItemChunk(
hostBaseUrl: string,
Expand All @@ -254,6 +314,8 @@ class duplicateObserver implements IWorkItemNotificationListener {
currentWorkItemTitle: string,
currentWorkItemDescription: string,
similarityIndex: number,
includeTitle: boolean,
includeDesciption: boolean,
workItemsChunk: Array<WorkItemReference>
): Promise<boolean> {
// Prepare our request body for this batch, only request title and description
Expand Down Expand Up @@ -312,7 +374,16 @@ class duplicateObserver implements IWorkItemNotificationListener {
this._logger.debug('description_match', description_match);

if (title_match + description_match > 0) {
const match: number = (title_match + description_match) / 2;
let match = 0;
// Let only include the results for relevant fields
if (includeTitle && includeDesciption) {
match = (title_match + description_match) / 2;
} else if (includeTitle) {
match = title_match;
} else {
match = description_match;
}

this._logger.debug('match', match);

if (match >= similarityIndex) {
Expand Down
6 changes: 5 additions & 1 deletion src/block-duplicate-project-admin.scss
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
@import "../node_modules/azure-devops-ui/Core/platformCommon.scss";
@import "../node_modules/azure-devops-ui/Core/platformCommon.scss";
.hidebullets {
list-style-type: none;
padding-inline-start: 20px;
}
80 changes: 74 additions & 6 deletions src/block-duplicate-project-admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {

interface IBlockDuplicatesAdminState {
SimilarityIndex: string;
IncludeTitle: boolean;
IncludeDesciption: boolean;
}

export default class BlockDuplicatesAdmin extends React.Component<
Expand All @@ -28,6 +30,8 @@ export default class BlockDuplicatesAdmin extends React.Component<

this.state = {
SimilarityIndex: '',
IncludeTitle: true,
IncludeDesciption: true,
};

this.onSaveClick = this.onSaveClick.bind(this);
Expand Down Expand Up @@ -65,13 +69,45 @@ export default class BlockDuplicatesAdmin extends React.Component<
return prevState;
});
}

const includeTitle: boolean = await dataManager.getValue<boolean>(
'IncludeTitle',
{
scopeType: 'Default',
}
);

if (includeTitle) {
this.setState((prevState: IBlockDuplicatesAdminState) => {
prevState.IncludeTitle = includeTitle;
return prevState;
});
}

const includeDesciption: boolean = await dataManager.getValue<boolean>(
'IncludeDesciption',
{
scopeType: 'Default',
}
);

if (includeDesciption) {
this.setState((prevState: IBlockDuplicatesAdminState) => {
prevState.IncludeDesciption = includeDesciption;
return prevState;
});
}
}

public async onSaveClick(): Promise<void> {
const config: IBlockDuplicatesAdminState = this.state;

const similarityIndex = Number(config.SimilarityIndex);
const includeTitle = config.IncludeTitle;
const includeDesciption = config.IncludeDesciption;
this._logger.debug(`Setting similarityIndex to ${similarityIndex}`);
this._logger.debug(`Setting includeTitle to ${includeTitle}`);
this._logger.debug(`Setting includeDesciption to ${includeDesciption}`);

const dataService: IExtensionDataService =
await SDK.getService<IExtensionDataService>(
Expand All @@ -86,6 +122,16 @@ export default class BlockDuplicatesAdmin extends React.Component<
await dataManager.setValue<number>('SimilarityIndex', similarityIndex, {
scopeType: 'Default',
});
await dataManager.setValue<boolean>('IncludeTitle', includeTitle, {
scopeType: 'Default',
});
await dataManager.setValue<boolean>(
'IncludeDesciption',
includeDesciption,
{
scopeType: 'Default',
}
);
}

public render(): JSX.Element {
Expand All @@ -106,9 +152,31 @@ export default class BlockDuplicatesAdmin extends React.Component<
<p>
Checks are automatically performed on work items of the{' '}
<strong>same type</strong> and on the following fields :
<ul>
<li>Title</li>
<li>Description</li>
<ul className="hidebullets">
<li>
<input
type="checkbox"
checked={config.IncludeTitle}
onChange={(e) => {
this.setState({
IncludeTitle: e.target.checked,
});
}}
/>
Title
</li>
<li>
<input
type="checkbox"
checked={config.IncludeDesciption}
onChange={(e) => {
this.setState({
IncludeDesciption: e.target.checked,
});
}}
/>
Description
</li>
</ul>
</p>
<p>
Expand All @@ -131,7 +199,7 @@ export default class BlockDuplicatesAdmin extends React.Component<
</ul>
</p>
<p>
Current Similarity Index threshold is:
Current Similarity Index threshold is:{' '}
<input
type="text"
onChange={(e) => {
Expand All @@ -143,10 +211,10 @@ export default class BlockDuplicatesAdmin extends React.Component<
/>
</p>
<p>
This extension can be leveraged in combination with the
This extension can be leveraged in combination with the{' '}
<a href="https://marketplace.visualstudio.com/items?itemName=tschmiedlechner.find-similar-workitems">
Find similar workitems
</a>
</a>{' '}
extension to establish which work items are similar to the current
item.
</p>
Expand Down

0 comments on commit 50bb8d3

Please sign in to comment.