Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

Commit

Permalink
FLOW-972 f-log re-write (#207)
Browse files Browse the repository at this point in the history
* FLOW-972 POC with nikhils logview code

* FLOW-972 percentage status added

* FLOW-972 search keyword feature

* FLOW-972 debounce added in search input

* FLOW-972 close search bar icon added

* FLOW-972 log-level filter added

* FLOW-972 code segregation

* FLOW-972 goto line feature

* FLOW-972 loglevels made configurable and stories updated

* FLOW-972 f-log playground updated

* FLOW-972 highlight custom keywords feature added with its respective story

* FLOW-972 unit tests added

* FLOW-972 sonar bug

* FLOW-972 lint checks fixed

* FLOW-972 changelogs updated

* FLOW-972 rebased

* FLOW-972 changelog updated
  • Loading branch information
vikas-cldcvr authored Dec 13, 2023
1 parent 19fee15 commit ee89b32
Show file tree
Hide file tree
Showing 18 changed files with 913 additions and 545 deletions.
9 changes: 8 additions & 1 deletion packages/flow-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

# Change Log

## [2.6.2] - 2023-12-12
## [2.6.3] - 2023-12-12

### Improvements

- `disable-result` attribute added on `f-search` to disable showing autocomplete results.
- `disable-suggestions` attribute added on `f-suggest` to disable showing autocomplete results.

## [2.6.2] - 2023-12-11

### Bug fixes

Expand Down
2 changes: 1 addition & 1 deletion packages/flow-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cldcvr/flow-core",
"version": "2.6.2",
"version": "2.6.3",
"description": "Core package of flow design system",
"module": "dist/flow-core.es.js",
"main": "dist/flow-core.cjs.js",
Expand Down
12 changes: 12 additions & 0 deletions packages/flow-core/src/components/f-search/f-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ export class FSearch extends FRoot {
@property({ reflect: true, type: String })
result?: FSearchSuggestions = [];

/**
* @attribute disable showing result
*/
@property({ reflect: true, type: Boolean, attribute: "disable-result" })
disableResult = false;

/**
* @attribute States are used to communicate purpose and connotations.
*/
Expand Down Expand Up @@ -176,6 +182,9 @@ export class FSearch extends FRoot {
@query("#helper-text-section")
helperTextSection!: FDiv;

@query("f-suggest")
suggestElement!: FSuggest;

/**
* @attribute assigned elements inside slot label
*/
Expand Down Expand Up @@ -225,6 +234,8 @@ export class FSearch extends FRoot {
} else {
this.value = String(e.detail.value);
}
} else {
this.value = undefined;
}
this.dispatchInputEvent(e.detail.value, this.selectedScope);
}
Expand Down Expand Up @@ -372,6 +383,7 @@ export class FSearch extends FRoot {
.category=${this.category}
.placeholder=${this.placeholder ?? "Search"}
.suggestions=${this.result}
.disableSuggestions=${this.disableResult}
icon-left=${this["search-button"] ? "" : "i-search"}
.state=${this.state}
?clear=${this.clear}
Expand Down
24 changes: 16 additions & 8 deletions packages/flow-core/src/components/f-suggest/f-suggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export class FSuggest extends FRoot {
@property({ reflect: false, type: Array })
suggestions?: FSuggestSuggestions = [];

/**
* @attribute disable showing suggestions
*/
@property({ reflect: true, type: Boolean, attribute: "disable-suggestions" })
disableSuggestions = false;

/**
* @attribute States are used to communicate purpose and connotations.
*/
Expand Down Expand Up @@ -222,14 +228,16 @@ export class FSuggest extends FRoot {
this.currentCategoryIndex = 0;
}
handleFocus() {
this.popOverElement.target = this.fInput.inputWrapperElement;
this.popOverElement.offset = {
mainAxis: 4
};
this.popOverElement.style.width = this.offsetWidth + "px";
this.popOverElement.style.maxHeight = this.optionsMaxHeight ?? "600px";
if (!this.loading) {
this.popOverElement.open = true;
if (!this.disableSuggestions) {
this.popOverElement.target = this.fInput.inputWrapperElement;
this.popOverElement.offset = {
mainAxis: 4
};
this.popOverElement.style.width = this.offsetWidth + "px";
this.popOverElement.style.maxHeight = this.optionsMaxHeight ?? "600px";
if (!this.loading) {
this.popOverElement.open = true;
}
}
}

Expand Down
15 changes: 15 additions & 0 deletions packages/flow-log/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

# Change Log

## [2.0.0] - 2023-12-11

### Major/Breaking Changes

- 🚫 Removed show-search and show-scrollbar attributes.
- 🪓 Dropped the internal dependency on xterm.js. (We're more independent now!)

### Features

- 🎨 Now rocking native HTML for log display. (Goodbye, unnecessary complexity!)
- 🚀 Handling logs like a champ with batch processing for over 1,000 lines. (Performance boost!)
- 🧰 Say hello to the show-toolbar attribute, bringing you filter, search, and Jump to Line tools. (Tools at your fingertips!)
- 🔍 Introducing the highlight-keywords property. Customize word highlighting to your heart's content. (Show off those important terms!)
- 🔄 Added the wrap-text attribute for toggling line wrapping. (Readability, you're in control!)

## [1.1.0] - 2023-11-27

### Minor Changes
Expand Down
12 changes: 5 additions & 7 deletions packages/flow-log/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cldcvr/flow-log",
"version": "1.1.0",
"version": "2.0.0",
"description": "Code editor component for flow library",
"module": "dist/flow-log.es.js",
"main": "dist/flow-log.es.js",
Expand All @@ -23,17 +23,15 @@
"dependencies": {
"@cldcvr/flow-core": "workspace:*",
"@cldcvr/flow-core-config": "workspace:*",
"anser": "^2.1.1",
"lit": "^3.1.0",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0",
"xterm-addon-search": "^0.11.0",
"xterm-addon-search-bar": "^0.2.0",
"xterm-addon-web-links": "^0.8.0"
"mark.js": "^8.11.1"
},
"devDependencies": {
"@custom-elements-manifest/analyzer": "^0.5.7",
"@open-wc/testing": "^3.1.5",
"@types/jest": "29.5.5",
"@types/mark.js": "^8.11.12",
"@web/dev-server-esbuild": "^0.4.1",
"@web/test-runner": "^0.17.1",
"concurrently": "^8.2.1",
Expand All @@ -45,7 +43,7 @@
"web-component-analyzer": "^2.0.0-next.4"
},
"peerDependencies": {
"@cldcvr/flow-core": "^2.0.3",
"@cldcvr/flow-core": "^2.6.2",
"@cldcvr/flow-core-config": "^1.1.3"
},
"repository": {
Expand Down
16 changes: 14 additions & 2 deletions packages/flow-log/src/components/f-log/f-log-global.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
@import "xterm/css/xterm.css";
@import "../../../../flow-core/src/mixins/scss/mixins";

f-log {
@include base();
display: flex;
flex: 1 0 auto;
width: 100%;
max-width: 100%;
max-height: 100%;
flex-direction: column;
}
f-div[direction="column"] {
> f-log {
width: 100%;
}
}

f-div[direction="row"] {
> f-log {
height: 100%;
}
}
126 changes: 126 additions & 0 deletions packages/flow-log/src/components/f-log/f-log-search-and-filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { FLog } from "./f-log";
import * as Mark from "mark.js";

export function closeSearchBar(this: FLog) {
this.showToolbar = false;
this.highlightText("");
}

/**
* Filter lines based on selected log level
* @param filterText
*/
export function filterLines(this: FLog, filterText: string) {
if (filterText.trim() !== "") {
const lines = this.shadowRoot?.querySelectorAll(".log-line");
if (lines) {
lines.forEach(element => {
if (!element.textContent?.toLowerCase()?.includes(filterText)) {
element.classList.add("hidden");
}
});
}
}
}
/**
* clear filtered lines
*/
export function clearFilter(this: FLog) {
const lines = this.shadowRoot?.querySelectorAll(".log-line.hidden");
if (lines) {
lines.forEach(element => {
element.classList.remove("hidden");
});
}
}

/**
* It will highlight text present in logs with mark tag
* @param searchText
*/
export function highlightText(this: FLog, searchText: string): void {
if (this.searchDebounceTimeout) {
clearTimeout(this.searchDebounceTimeout);
}
this.searchDebounceTimeout = setTimeout(() => {
const markInstance = new Mark(
this.shadowRoot?.querySelectorAll(".log-line:not(.hidden)") as NodeListOf<HTMLSpanElement>
);
if (this.lastSearchValue && this.lastSearchValue !== "") {
markInstance.unmark(this.lastSearchValue as unknown as HTMLElement);
}
this.lastSearchValue = searchText;
if (searchText && searchText !== "") {
markInstance.mark(searchText, {
done: (occurrences: number) => {
this.searchOccurrences = occurrences;
const firstMark = this.shadowRoot?.querySelector("mark[data-markjs='true']");
firstMark?.scrollIntoView({ block: "start", behavior: "smooth" });
firstMark?.classList.add("active");
this.currentMarkIndex = 0;
if (occurrences > 0 && this.searchInput) {
this.searchInput.suggestElement.suffix = `1 of ${occurrences}`;
} else if (this.searchInput) {
this.searchInput.suggestElement.suffix = `No results`;
}
}
});
} else {
if (this.searchInput) this.searchInput.suggestElement.suffix = undefined;
this.searchOccurrences = 0;
}
}, 500);
}

/**
* when next arrow is clicked, it will go to next highlighted mark
*/
export function nextMark(this: FLog) {
if (this.searchOccurrences > 0 && this.allMarks) {
this.allMarks[this.currentMarkIndex].classList.remove("active");
this.currentMarkIndex += 1;
if (this.currentMarkIndex === this.allMarks.length) {
this.currentMarkIndex = 0;
}
this.allMarks[this.currentMarkIndex].scrollIntoView({ block: "start", behavior: "smooth" });
this.allMarks[this.currentMarkIndex].classList.add("active");
if (this.searchInput) {
this.searchInput.suggestElement.suffix = `${this.currentMarkIndex + 1} of ${
this.searchOccurrences
}`;
}
}
}

/**
* when previous arrow is clicked, it will go to previous highlighted mark
*/
export function prevMark(this: FLog) {
if (this.searchOccurrences > 0 && this.allMarks) {
this.allMarks[this.currentMarkIndex].classList.remove("active");
this.currentMarkIndex -= 1;
if (this.currentMarkIndex === -1) {
this.currentMarkIndex = this.allMarks.length - 1;
}
this.allMarks[this.currentMarkIndex].scrollIntoView({ block: "start", behavior: "smooth" });
this.allMarks[this.currentMarkIndex].classList.add("active");
if (this.searchInput) {
this.searchInput.suggestElement.suffix = `${this.currentMarkIndex + 1} of ${
this.searchOccurrences
}`;
}
}
}

export function handleLogLevelFilter(this: FLog, event: CustomEvent<{ scope: string }>) {
this.clearFilter();
const logLevel = event.detail.scope;
if (logLevel && logLevel !== "ALL") {
this.filterLines(logLevel.toLowerCase());
}
}

export function handleSearch(this: FLog, event: CustomEvent<{ value: string; scope: string }>) {
this.highlightText(event.detail.value);
this.handleLogLevelFilter(event);
}
69 changes: 69 additions & 0 deletions packages/flow-log/src/components/f-log/f-log-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
export type HexColor = `#${string}`;
export type HighlightKeywords = Record<string, HexColor>;
// Adds some general formatting/highlighting to logs
export function formatLogLine(logLine: string, highlightKeywords?: HighlightKeywords): string {
// Highlight [xxx] with a greyed out version
let newLine = logLine
// Highlight quoted strings
.replace(/("[^"]*?"|'[^']*?')/g, '<span style="color: var(--color-warning-subtle)">$1</span>')

// Highlight bracket contents
// 100 is an arbitrary limit to prevent catastrophic backtracking in Regex
.replace(
/(\[[^\]]{0,100}\])/g,
'<span style="color:var(--color-text-subtle); font-weight: bold;">$1</span>'
)

// Highlight potential dates (YYYY/MM/DD HH:MM:SS)
.replace(
/(\d{1,4}\/\d{1,2}\/\d{1,4}(?: \d{1,2}:\d{1,2}:\d{1,2})?)/g,
'<span style="color: #68c9f2">$1</span>'
)

// Highlight potential dates (YYYY-MM-DD with timezone)
.replace(
/(\d{1,4}-\d{1,2}-\d{1,4}(?:\s?T?\d{1,2}:\d{1,2}:[\d.]{1,10})?Z?)/g,
'<span style="color: #68c9f2">$1</span>'
)

// Highlight YAML keys
// 100 is an arbitrary limit to prevent catastrophic backtracking in Regex
.replace(
/(^\s*[-\sa-z0-9_]{1,100}:\s)/gi,
'<span style="color:var(--color-primary-default)">$1</span>'
)

// Highlight urls
.replace(
/((https?:\/\/)([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}(:[0-9]+)?(\/[^" ]*)?)/g,
'<a style="color: var(--color-primary-default); text-decoration: underline" href="$1" rel="noopener" target="_blank">$1</a>'
);

if (highlightKeywords) {
Object.entries(highlightKeywords).forEach(([keyword, color]) => {
if (isValidHexColor(color)) {
newLine = newLine.replace(
new RegExp(`(${keyword})`),
`<span style="color:${color}">$1</span>`
);
} else {
console.warn(`${color as string} is not valid hex`);
}
});
}

if (/(ERROR|FAILED)/gi.test(newLine)) {
newLine = `<span style="background: var(--color-danger-subtle)">${newLine}</span>`;
}

return newLine;
}

export function isValidHexColor(value: string): value is HexColor {
const hexColorRegex = /^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/;
return hexColorRegex.test(value);
}

export function getUniqueStringId() {
return `${new Date().valueOf()}`;
}
Loading

0 comments on commit ee89b32

Please sign in to comment.