Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
Mateusz Duda committed Mar 27, 2024
1 parent a9a99fb commit 2693ecf
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 89 deletions.
73 changes: 40 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,57 +54,64 @@ npm i scope-tags -D
"logsURL"
```

### Local development

1. Clone this repository
2. Run `npm install` (preffered Node/NPM version: v12.16.3 / 6.14.4)
3. Run `npm link`
4. Use the local version of the script using `scope` (without the `npx prefix`) (if it doesn't work - restart the terminal)
5. `npx scope` runs version installed in the current repository (as a dependency), `scope` runs locally builded version

### Features to do

- [x] Import `ts-morph` library
- [x] Test if it reads the project data correctly
- [x] Find relative tsconfig.json
- [x] Add support for multiple tsconfig.json's
- [x] `.scope` metadata initialization
- [x] `tags.json`
- [x] `database.json`
- [x] Basic command line interface and tag management:
- [x] Reading `tags.json`
- [x] Adding, deleting, updating tags
- [x] Basic file to module mapping
- [x] Tags should have (possibly nested) categories
- [x] Instead of git notes, which are not shared by default, we should check commit based on if any of the files is not present in database
- [x] Add scope report generation
- [x] Get fileData for affected files
- [x] Get tags for affected files
- [x] Create report with affected modules
- [x] Add report to task (build integration)
- [x] Wywalić build tag bo jest zbędny
- [x] Custom import map for `.yaml` files
- [x] Read external import map if configured
- [x] Test if it correctly identifies imported yaml file
- [ ] Some kind of debugging options:
- [ ] --see -> to be able to see which files are
- [ ] Unit tests for common actions:
- [ ] Testing if files are correstly updated in database depending on changes in git
- [ ] Add [np package](https://www.npmjs.com/package/np) to handle publishing to npm
- [x] Import `ts-morph` library
- [x] Test if it reads the project data correctly
- [x] Find relative tsconfig.json
- [x] Add support for multiple tsconfig.json's
- [x] `.scope` metadata initialization
- [x] `tags.json`
- [x] `database.json`
- [x] Basic command line interface and tag management:
- [x] Reading `tags.json`
- [x] Adding, deleting, updating tags
- [x] Basic file to module mapping
- [x] Tags should have (possibly nested) categories
- [x] Instead of git notes, which are not shared by default, we should check commit based on if any of the files is not present in database
- [x] Add scope report generation
- [x] Get fileData for affected files
- [x] Get tags for affected files
- [x] Create report with affected modules
- [x] Add report to task (build integration)
- [x] Custom import map for `.yaml` files
- [x] Read external import map if configured
- [x] Test if it correctly identifies imported yaml file
- [x] Some kind of debugging options:
- [x] --see -> to be able to see which files are

### Assertions to add

- [ ] On loading `tags.json` assert that all parents exist in database, if not then these modules won't be displayed

### Nice to have

- [x] Use [spinner](https://www.npmjs.com/package/ora) while waiting for async operations (opening repo, ast analysis) - probably not needed because wait time is short
- [-] If eslint available, compare changed files before and after linting. Then, ommit files which only have these changes from scope report - won't be needed because of git option to discard whitespaces and empty lines
- [-] Use [spinner](https://www.npmjs.com/package/ora) while waiting for async operations (opening repo, ast analysis) - probably not needed because wait time is short
- [x] Github workflows with tests
- [ ] Changes severity using [survey prompt](https://github.com/enquirer/enquirer#scale-prompt)? Severity based on git diffs?
- [-] If eslint available, compare changed files before and after linting. Then, ommit files which only have these changes from scope report - won't be needed because of git option to discard whitespaces and empty lines
- [x] Changes severity using [survey prompt](https://github.com/enquirer/enquirer#scale-prompt)? Severity based on git diffs? - Added change `relevancy`
- [ ] Add keyboard shortcut hints when selecting tags and files -> https://github.com/enquirer/enquirer#select-choices
- [ ] Add groups on select prompt:
- [ ] Group files based on common path (files from same directory sould be grouped)
- [ ] Group tags based on parent modules
- [ ] Add remove hanging tags option to tag manager - search for tags not assignes to any module and ask the user if they want to delete them
- [ ] Add remove hanging tags option to tag manager - search for tags not assigned to any module and ask the user if they want to delete them
- [ ] Unit tests for common actions:
- [ ] Testing if files are correstly updated in database depending on changes in git
- [ ] Add [np package](https://www.npmjs.com/package/np) to handle publishing to npm - giga optional

### To be discussed

- [ ] Testing approach - how each functionality should be tested?
- Is creating a separate test git repository for each test a good idea? (too much hustle)
- [ ] What actions can be performed on files?
- Adding
- Adding

### Architecture diagram

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "scope-tags",
"version": "0.2.1",
"version": "0.2.2",
"description": "Output human readable test scope report for QA",
"main": "dist/scope.js",
"types": "dist/scope.d.ts",
Expand Down
3 changes: 1 addition & 2 deletions src/Commands/runAddCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export function runAddCommand(args: Array<string>, root: string) {
try {
fileDataRelevancy = await relevancyTagger.start(fileDataToTag);
} catch (e) {
console.log((e as Error)?.message);
console.log("[Scope tags] Could not add relevancy, the changes won't be saved.");
return;
}
Expand All @@ -57,7 +56,7 @@ export function runAddCommand(args: Array<string>, root: string) {
commit message: ${mostRecentCommitMessage}`);
}

await repository.amendMostRecentCommit(fileTagsDatabase.getPath(), newCommitMessage);
await repository.amendMostRecentCommit([fileTagsDatabase.getPath(), tagsDefinitionFile.getPath()], newCommitMessage);

}); // TODO: Save already tagged files
}).catch(e => console.log("Canceled")); // TODO: Save already tagged files - ???
Expand Down
126 changes: 96 additions & 30 deletions src/Console/FileTagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,25 @@ import { FileData } from "../Git/Types";
import { FileTagsDatabase, TagIdentifier } from "../Scope/FileTagsDatabase";
import { TagsDefinitionFile } from "../Scope/TagsDefinitionFile";
import { TagManager } from "./TagManager";
import { YesNoMenu } from "./YesNoMenu";
import { getFileDirectoryPath } from "../FileSystem/fileSystemUtils";

const { MultiSelect } = require('enquirer');

type FileAsOption = { name: string, value: FileData } | { message: string, role: string };
type BasicFileAsOption = { name: string, value: FileData };

type FileAsOption = BasicFileAsOption
| { message: string, role: string }
| { disabled: boolean, hint: string, choices: BasicFileAsOption[] };

type FileOrIgnored = { path: string, ignored: boolean };
type FileToReassignTagsAsOption = { name: string, value: FileOrIgnored }

export class FileTagger {

private _tags: TagsDefinitionFile;
private _database: FileTagsDatabase;
private _repository: GitRepository;

constructor(tags: TagsDefinitionFile, database: FileTagsDatabase, repository: GitRepository) {
this._tags = tags;
this._database = database;
this._repository = repository;
constructor(
private _tags: TagsDefinitionFile,
private _database: FileTagsDatabase,
private _repository: GitRepository) {
}

public async start(fileData: Array<FileData>, filterAlreadyTaggedFiles = true) {
Expand All @@ -45,11 +46,24 @@ export class FileTagger {

try {
const selectedTags: Array<TagIdentifier> = await tagManager.selectMultipleTagIdentifiers(commonTagIdentifiers);
for (const file of selectedFiles) {
tagsMappedToFiles.set(file, selectedTags);

if (!selectedTags.length) {
// Confirmation to ignore files
if (await this._isUserSureToIgnoreFiles(selectedFiles)) {
for (const file of selectedFiles) {
tagsMappedToFiles.set(file, []);
}
untaggedFiles = untaggedFiles.filter(file => !selectedFiles.includes(file));
}
} else {
for (const file of selectedFiles) {
tagsMappedToFiles.set(file, selectedTags);
}
untaggedFiles = untaggedFiles.filter(file => !selectedFiles.includes(file));
}
untaggedFiles = untaggedFiles.filter(file => !selectedFiles.includes(file));
} catch (e) { }
} catch (e) {
// Do nothing, user just exited from tag selection
}
}

const fileDataToReturn: Array<FileData> = [];
Expand All @@ -70,6 +84,14 @@ export class FileTagger {
return fileDataToReturn;
}

private async _isUserSureToIgnoreFiles(selectedFiles: FileData[]): Promise<boolean> {
return await (new YesNoMenu()).ask(
"Are you sure you want to ignore these files? (they won't be checked in the future)",
true,
selectedFiles.map(fileData => fileData.newPath).join('\n') + '\n',
);
}

private _getCommonTagIdentifiers(selectedFiles: FileData[]): Array<TagIdentifier> {
const uniqueTagIdentifiers: Array<TagIdentifier> = [];

Expand Down Expand Up @@ -138,12 +160,10 @@ export class FileTagger {
private async _selectFiles(fileData: Array<FileData>): Promise<FileData[]> {
const prompt = new MultiSelect({
name: 'value',
message: 'Select files to apply the same tags (or CTRL+C for next step)',
limit: 7,
choices: [
...this._mapFileDataToOptions(fileData),
{ role: "separator" },
],
message: 'Select files to apply the same tags',
footer: 'CTRL+C to go to next step, G to select whole group',
limit: 10,
choices: this._mapFileDataToOptions(fileData),
result(value: any) {
return this.map(value);
},
Expand All @@ -163,34 +183,80 @@ export class FileTagger {
}
}

private _createSeparatorMessageFromCommit(commit: Commit | undefined) {
private _createSeparatorMessage(commit?: Commit) {
return {
message: `── ${commit?.summary() || "Unknown commit"} ──`,
role: "separator"
};
}

private _createGroupedOption(fileData: FileData[], groupName: string) {
return {
choices: this._createOptions(fileData),
hint: groupName,
disabled: true
};
}

private _createOptions(fileData: FileData[]) {
return fileData.map(data => ({ name: data.newPath, value: data }));
}

private _mapFileDataToOptions(fileData: Array<FileData>): FileAsOption[] {
if (fileData.length === 0) {
throw new Error("[FileTagger] Cannot have empty list of files to tag.");
}

let currentCommit = fileData[0].commitedIn;
const fileAsOptionArray: FileAsOption[] = [];

const fileAsOptionArray: FileAsOption[] = [this._createSeparatorMessageFromCommit(currentCommit)];
const commitToFileDataMap: Map<Commit, FileData[]> = new Map();

const uniqueCommits: Commit[] = [];
fileData.forEach(file => {
if (file.commitedIn !== currentCommit) {
currentCommit = file.commitedIn;
fileAsOptionArray.push(this._createSeparatorMessageFromCommit(currentCommit));
if (file.commitedIn && !uniqueCommits.includes(file.commitedIn)) {
uniqueCommits.push(file.commitedIn);
}
});

fileAsOptionArray.push({
name: file.oldPath, // TODO: What about newPath?
value: file
})
// console.log(uniqueCommits);

uniqueCommits.forEach(uniqueCommit => {
commitToFileDataMap.set(uniqueCommit, fileData.filter(data => data.commitedIn === uniqueCommit));
});

// Group files based on the same directory
commitToFileDataMap.forEach((matchingFileData: FileData[], commit: Commit, map: Map<Commit, FileData[]>) => {
fileAsOptionArray.push(this._createSeparatorMessage(commit));

// Group files with a common source directory
const processedFileData: FileData[] = [];

matchingFileData.forEach(dataA => {
const currentDirectoryPath = getFileDirectoryPath(dataA.newPath);

const groupedFiles = matchingFileData.filter(dataB => !processedFileData.includes(dataB)
&& getFileDirectoryPath(dataB.newPath) === currentDirectoryPath);

if (groupedFiles.length > 1) {
// Add as group
const option = this._createGroupedOption(groupedFiles, currentDirectoryPath);
fileAsOptionArray.push(option);
processedFileData.push(...groupedFiles);
}
});

const ungroupedFileData = matchingFileData.filter(data => !processedFileData.includes(data));
fileAsOptionArray.push(...this._createOptions(ungroupedFileData));
});

// Append files with unknown source commit
const fileDataWithUnknownSourceCommit = fileData.filter(data => !data.commitedIn);

if (fileDataWithUnknownSourceCommit.length) {
fileAsOptionArray.push(this._createSeparatorMessage());
fileAsOptionArray.push(...this._createOptions(fileDataWithUnknownSourceCommit));
}

return fileAsOptionArray;
}
}
Loading

0 comments on commit 2693ecf

Please sign in to comment.