-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Side by side comparison feature
- Loading branch information
1 parent
234cdcd
commit 778efd9
Showing
17 changed files
with
3,187 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
name: CI | ||
|
||
on: | ||
push: | ||
pull_request: | ||
workflow_dispatch: | ||
inputs: | ||
type: | ||
description: 'Release type (major/minor/patch)' | ||
default: 'patch' | ||
required: true | ||
type: choice | ||
options: | ||
- major | ||
- minor | ||
- patch | ||
|
||
jobs: | ||
ci: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Exit if workflow_dispatch on other branch than main | ||
if: (github.event_name == 'workflow_dispatch') && (github.ref_name != 'main') | ||
run: | | ||
echo "Release workflow should be runned from main branch, exiting." | ||
exit 1 | ||
- uses: actions/setup-node@v3 | ||
with: | ||
node-version: 18 | ||
- name: Setup Git | ||
run: | | ||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
git config --local user.name "github-actions[bot]" | ||
- name: Install dependencies | ||
run: npm ci | ||
|
||
# Only runs on release | ||
- name: Set release version | ||
if: github.event_name == 'workflow_dispatch' | ||
run: | | ||
npm version ${{ github.event.inputs.type }} | ||
echo version=$(node -p "require('./package.json').version") >> $GITHUB_ENV | ||
- name: Package | ||
run: npm run package | ||
- name: Upload artifact | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: vscode-vsix | ||
path: "*.vsix" | ||
retention-days: 5 | ||
|
||
# All tasks below only run on release | ||
- name: Push changes | ||
uses: ad-m/github-push-action@master | ||
if: github.event_name == 'workflow_dispatch' | ||
with: | ||
github_token: ${{ secrets.GITHUB_TOKEN }} | ||
tags: true | ||
branch: main | ||
- name: Upload release | ||
uses: softprops/action-gh-release@v1 | ||
if: github.event_name == 'workflow_dispatch' | ||
with: | ||
tag_name: "v${{ env.version }}" | ||
files: "*.vsix" |
15 changes: 15 additions & 0 deletions
15
docs/developer-documentation/constants/enums/SelectedRowType.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
|
||
--- | ||
```TS | ||
enum SelectedRowType { | ||
None = "NONE", | ||
UserSelect = "SELECTED", | ||
QueryResult = "QUERY_RESULT", | ||
} | ||
``` | ||
|
||
This `enum` is used for the Structure Matching functionality to indicate if the row is selected by the user. | ||
|
||
`NONE` if the row is not selected; | ||
`SELECTED` if the row is selected by the user; | ||
`QUERY_RESULT` it is not used yet. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
import Rule from "./rules/Rule"; | ||
import { extent } from "d3-array"; | ||
import { Header } from "./types"; | ||
import { scaleSequential } from "d3-scale"; | ||
import { interpolateTurbo } from "d3-scale-chromatic"; | ||
|
||
const DEFAULT_HEADER_TYPE = "string"; | ||
const HEADER_TYPE_LOOKUP = { | ||
threadID: "number", | ||
}; | ||
|
||
export default class LogFile { | ||
private readonly headerIndexLookup: { [k: string]: number }; | ||
|
||
readonly contentHeaders: string[]; | ||
readonly rows: string[][]; | ||
readonly columnsColors: string[][] = []; | ||
headers: Header[]; | ||
selectedColumns: boolean[]; | ||
selectedColumnsMini: boolean[]; | ||
|
||
private constructor(contentHeaders: string[], headers: Header[], rows: string[][]) { | ||
this.contentHeaders = contentHeaders; | ||
this.headerIndexLookup = Object.fromEntries(headers.map((h, i) => [h.name, i])); | ||
this.headers = headers; | ||
this.rows = rows; | ||
this.selectedColumns = new Array(headers.length).fill(true); | ||
this.selectedColumnsMini = new Array(headers.length).fill(true); | ||
this.selectedColumnsMini[0] = false; | ||
} | ||
|
||
static create(content: { [s: string]: string }[], rules: Rule[]) { | ||
const contentHeaders = this.getContentHeaders(content); | ||
const headers = this.getHeaders(contentHeaders, rules); | ||
if (!contentHeaders.includes("Line")) | ||
for (let i = 0; i < content.length; i++) | ||
content[i]["Line"] = (i + 1).toString(); | ||
const rows = content.map((l) => headers.map((h) => l[h.name])); | ||
const logFile = new LogFile(contentHeaders, headers, rows); | ||
logFile.computeDefaultColumnColors(); | ||
logFile.computeRulesValuesAndColors(rules); | ||
return logFile; | ||
} | ||
|
||
updateLogFile(rules: Rule[], structureMatches: number[][]): LogFile { | ||
const [updatedSelected, updatedSelectedMini] = this.updateSelectedColumns(rules) | ||
const headers = LogFile.getHeaders(this.contentHeaders, rules); | ||
|
||
let rows = this.rows; | ||
|
||
if (structureMatches.length > 0) { | ||
let num = 1; | ||
while (true) { | ||
const name = "Structure" + num | ||
const type = DEFAULT_HEADER_TYPE; | ||
if (!headers.map(h => h.name).includes(name)) { | ||
headers.push({ name, type }); | ||
break; | ||
} | ||
num++; | ||
} | ||
// Previous structure match already exists in logfile | ||
if (headers.length === rows[0].length) { | ||
for (let i = 0; i < rows.length; i++) | ||
rows[i].pop(); | ||
} | ||
else { | ||
updatedSelected.push(true); | ||
updatedSelectedMini.push(true); | ||
} | ||
for (let i = 0; i < rows.length; i++) | ||
rows[i].push("0"); | ||
|
||
let currentStructureIndex = 0; | ||
for (let i = 0; i < rows.length; i++) { | ||
if (currentStructureIndex < structureMatches.length) { | ||
if (i > structureMatches[currentStructureIndex].at(-1)!) { | ||
currentStructureIndex++; | ||
if (currentStructureIndex === structureMatches.length) | ||
break; | ||
} | ||
|
||
if (structureMatches[currentStructureIndex].includes(i)) { | ||
rows[i].pop(); | ||
rows[i].push((currentStructureIndex + 1).toString()); | ||
} | ||
} | ||
} | ||
} | ||
|
||
const logFile = new LogFile(this.contentHeaders, headers, rows); | ||
logFile.copyDefaultColumnColors(this.columnsColors); | ||
logFile.computeRulesValuesAndColors(rules); | ||
return logFile.setSelectedColumns(updatedSelected, updatedSelectedMini); | ||
} | ||
|
||
updateSelectedColumns(rules: Rule[]) { | ||
const existingHeaders = this.headers.map(h => h.name); | ||
const updatedSelected = this.selectedColumns.slice(0, this.contentHeaders.length); | ||
const updatedSelectedMini = this.selectedColumnsMini.slice(0, this.contentHeaders.length); | ||
|
||
for (let i = 0; i < rules.length; i++) { | ||
const existingIndex = existingHeaders.indexOf(rules[i].column); | ||
if (existingIndex > -1) { | ||
updatedSelected.push(this.selectedColumns[existingIndex]); | ||
updatedSelectedMini.push(this.selectedColumnsMini[existingIndex]); | ||
} | ||
else { | ||
updatedSelected.push(true); | ||
updatedSelectedMini.push(true); | ||
} | ||
} | ||
return [updatedSelected, updatedSelectedMini] | ||
} | ||
|
||
setSelectedColumns(selected: boolean[], selectedMini: boolean[]) { | ||
for (let column = 0; column < this.selectedColumns.length; column++) { | ||
if (selected[column] !== undefined) { | ||
this.selectedColumns[column] = selected[column]; | ||
} | ||
} | ||
for (let column = 0; column < this.selectedColumnsMini.length; column++) { | ||
if (selectedMini[column] !== undefined) { | ||
this.selectedColumnsMini[column] = selectedMini[column]; | ||
} | ||
} | ||
return this; | ||
} | ||
|
||
getSelectedHeader(): Header[] { | ||
const selectedHeaders = this.headers.filter((h, i) => this.selectedColumns[i] == true); | ||
return selectedHeaders; | ||
} | ||
|
||
getSelectedHeaderMini(): Header[] { | ||
const selectedHeadersMini = this.headers.filter((h, i) => this.selectedColumnsMini[i] == true); | ||
return selectedHeadersMini; | ||
} | ||
|
||
private static getContentHeaders(content: { [s: string]: string }[]) { | ||
// Headers are all keys that are present in the first object (row) | ||
const firstRow = content[0] ?? {}; | ||
const contentHeaders = Object.keys(firstRow); | ||
return contentHeaders; | ||
} | ||
|
||
private static getHeaders(contentHeaders: string[], rules: Rule[]) { | ||
const allHeaders = [...contentHeaders, ...rules.map((r) => r.column)]; | ||
let headers = allHeaders.map((name) => { | ||
const type = HEADER_TYPE_LOOKUP[name] ?? DEFAULT_HEADER_TYPE; | ||
return { name, type }; | ||
}); | ||
if (!contentHeaders.includes("Line")) { | ||
const lineHeader = [{ name: "Line", type: DEFAULT_HEADER_TYPE }]; | ||
headers = lineHeader.concat(headers); | ||
} | ||
return headers; | ||
} | ||
|
||
|
||
private computeDefaultColumnColors() { | ||
for (let i = 0; i < this.getStaticHeadersSize(); i++) { | ||
const values = this.rows.map((r) => r[i]); | ||
this.columnsColors[i] = LogFile.computeColors(this.headers[i], values); | ||
} | ||
} | ||
|
||
private copyDefaultColumnColors(colours: string[][]) { | ||
for (let i = 0; i < this.getStaticHeadersSize(); i++) { | ||
this.columnsColors[i] = colours[i]; | ||
} | ||
} | ||
|
||
private computeRulesValuesAndColors(rules: Rule[]) { | ||
// Compute rules values | ||
const firstRuleIndex = this.getStaticHeadersSize(); | ||
const rulesValues = rules.map((r) => r.computeValues(this)); | ||
for (let row = 0; row < this.rows.length; row++) { | ||
for (let column = 0; column < rulesValues.length; column++) { | ||
this.rows[row][column + firstRuleIndex] = rulesValues[column][row]; | ||
} | ||
} | ||
|
||
// Compute colors | ||
for (let i = firstRuleIndex; i < this.headers.length; i++) { | ||
const values = this.rows.map((r) => r[i]); | ||
this.columnsColors[i] = LogFile.computeColors(this.headers[i], values); | ||
} | ||
} | ||
|
||
private static computeColors(header: Header, values: string[]) { | ||
let colorizer: (s: string) => string; | ||
if (this.containsOnlyNumbers(values)) { | ||
let uniqueValues = [...new Set(values)]; | ||
const sortedNumbers = uniqueValues.map(Number).sort(function (a, b) { return a - b; }); | ||
colorizer = scaleSequential().domain(extent(sortedNumbers)).interpolator(interpolateTurbo); | ||
} else if (header.type === "string") { | ||
const uniqueValues = [...new Set(values)].sort(); | ||
colorizer = (v) => interpolateTurbo(uniqueValues.indexOf(v) / uniqueValues.length); | ||
} | ||
|
||
return values.map((l) => colorizer(l)); | ||
} | ||
|
||
private static containsOnlyNumbers(items: string[]) { | ||
for (const i of items) { | ||
if (!Number(+i) && (+i !== 0)) | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
private getStaticHeadersSize() { | ||
let size = this.contentHeaders.length; | ||
if (!this.contentHeaders.includes("Line")) | ||
size++; | ||
return size; | ||
} | ||
|
||
amountOfRows = () => this.rows.length; | ||
amountOfColumns = () => this.headers.length; | ||
amountOfColorColumns = () => this.columnsColors.length; | ||
|
||
value(column: string, row: number): string { | ||
return this.rows[row]?.[this.headerIndexLookup[column]]; | ||
} | ||
} |
Oops, something went wrong.