1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Cleanup SearchStore (#2299)

This commit is contained in:
Sebastian Malton 2021-03-12 16:26:02 -05:00 committed by GitHub
parent 373d2d2a47
commit c0d18ff19e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 61 deletions

View File

@ -29,28 +29,28 @@ describe("search store tests", () => {
expect(searchStore.occurrences).toEqual([]); expect(searchStore.occurrences).toEqual([]);
}); });
it("find 3 occurences across 3 lines", () => { it("find 3 occurrences across 3 lines", () => {
searchStore.onSearch(logs, "172"); searchStore.onSearch(logs, "172");
expect(searchStore.occurrences).toEqual([0, 1, 2]); expect(searchStore.occurrences).toEqual([0, 1, 2]);
}); });
it("find occurences within 1 line (case-insensitive)", () => { it("find occurrences within 1 line (case-insensitive)", () => {
searchStore.onSearch(logs, "Starting"); searchStore.onSearch(logs, "Starting");
expect(searchStore.occurrences).toEqual([2, 2]); expect(searchStore.occurrences).toEqual([2, 2]);
}); });
it("sets overlay index equal to first occurence", () => { it("sets overlay index equal to first occurrence", () => {
searchStore.onSearch(logs, "Replica"); searchStore.onSearch(logs, "Replica");
expect(searchStore.activeOverlayIndex).toBe(0); expect(searchStore.activeOverlayIndex).toBe(0);
}); });
it("set overlay index to next occurence", () => { it("set overlay index to next occurrence", () => {
searchStore.onSearch(logs, "172"); searchStore.onSearch(logs, "172");
searchStore.setNextOverlayActive(); searchStore.setNextOverlayActive();
expect(searchStore.activeOverlayIndex).toBe(1); expect(searchStore.activeOverlayIndex).toBe(1);
}); });
it("sets overlay to last occurence", () => { it("sets overlay to last occurrence", () => {
searchStore.onSearch(logs, "172"); searchStore.onSearch(logs, "172");
searchStore.setPrevOverlayActive(); searchStore.setPrevOverlayActive();
expect(searchStore.activeOverlayIndex).toBe(2); expect(searchStore.activeOverlayIndex).toBe(2);
@ -62,7 +62,7 @@ describe("search store tests", () => {
}); });
it("escapes string for using in regex", () => { it("escapes string for using in regex", () => {
const regex = searchStore.escapeRegex("some.interesting-query\\#?()[]"); const regex = SearchStore.escapeRegex("some.interesting-query\\#?()[]");
expect(regex).toBe("some\\.interesting\\-query\\\\\\#\\?\\(\\)\\[\\]"); expect(regex).toBe("some\\.interesting\\-query\\\\\\#\\?\\(\\)\\[\\]");
}); });

View File

@ -3,9 +3,34 @@ import { dockStore } from "../renderer/components/dock/dock.store";
import { autobind } from "../renderer/utils"; import { autobind } from "../renderer/utils";
export class SearchStore { export class SearchStore {
@observable searchQuery = ""; // Text in the search input /**
@observable occurrences: number[] = []; // Array with line numbers, eg [0, 0, 10, 21, 21, 40...] * An utility methods escaping user string to safely pass it into new Regex(variable)
@observable activeOverlayIndex = -1; // Index withing the occurences array. Showing where is activeOverlay currently located * @param value Unescaped string
*/
public static escapeRegex(value?: string): string {
return value ? value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&") : "";
}
/**
* Text in the search input
*
* @observable
*/
@observable searchQuery = "";
/**
* Array with line numbers, eg [0, 0, 10, 21, 21, 40...]
*
* @observable
*/
@observable occurrences: number[] = [];
/**
* Index within the occurrences array. Showing where is activeOverlay currently located
*
* @observable
*/
@observable activeOverlayIndex = -1;
constructor() { constructor() {
reaction(() => dockStore.selectedTabId, () => { reaction(() => dockStore.selectedTabId, () => {
@ -19,49 +44,45 @@ export class SearchStore {
* @param query Search query from input * @param query Search query from input
*/ */
@action @action
onSearch(text: string[], query = this.searchQuery) { public onSearch(text?: string[] | null, query = this.searchQuery): void {
this.searchQuery = query; this.searchQuery = query;
if (!query) { if (!query) {
this.reset(); return this.reset();
}
this.occurrences = this.findOccurrences(text ?? [], query);
if (!this.occurrences.length) {
return; return;
} }
this.occurrences = this.findOccurences(text, query);
if (!this.occurrences.length) return;
// If new highlighted keyword in exact same place as previous one, then no changing in active overlay // If new highlighted keyword in exact same place as previous one, then no changing in active overlay
if (this.occurrences[this.activeOverlayIndex] !== undefined) return; if (this.occurrences[this.activeOverlayIndex] === undefined) {
this.activeOverlayIndex = this.getNextOverlay(true); this.activeOverlayIndex = this.getNextOverlay(true);
}
} }
/** /**
* Does searching within text array, create a list of search keyword occurences. * Does searching within text array, create a list of search keyword occurrences.
* Each keyword "occurency" is saved as index of the the line where keyword founded * Each keyword "occurrence" is saved as index of the line where keyword was found
* @param text An array of any textual data (logs, for example) * @param lines An array of any textual data (logs, for example)
* @param query Search query from input * @param query Search query from input
* @returns {Array} Array of line indexes [0, 0, 14, 17, 17, 17, 20...] * @returns Array of line indexes [0, 0, 14, 17, 17, 17, 20...]
*/ */
findOccurences(text: string[], query: string) { private findOccurrences(lines: string[], query?: string): number[] {
if (!text) return []; const regex = new RegExp(SearchStore.escapeRegex(query), "gi");
const occurences: number[] = [];
text.forEach((line, index) => { return lines
const regex = new RegExp(this.escapeRegex(query), "gi"); .flatMap((line, index) => Array.from(line.matchAll(regex), () => index));
const matches = [...line.matchAll(regex)];
matches.forEach(() => occurences.push(index));
});
return occurences;
} }
/** /**
* Getting next overlay index within the occurences array * Getting next overlay index within the occurrences array
* @param loopOver Allows to jump from last element to first * @param loopOver Allows to jump from last element to first
* @returns {number} next overlay index * @returns next overlay index
*/ */
getNextOverlay(loopOver = false) { private getNextOverlay(loopOver = false): number {
const next = this.activeOverlayIndex + 1; const next = this.activeOverlayIndex + 1;
if (next > this.occurrences.length - 1) { if (next > this.occurrences.length - 1) {
@ -72,11 +93,11 @@ export class SearchStore {
} }
/** /**
* Getting previous overlay index within the occurences array of occurences * Getting previous overlay index within the occurrences array of occurrences
* @param loopOver Allows to jump from first element to last one * @param loopOver Allows to jump from first element to last one
* @returns {number} prev overlay index * @returns previous overlay index
*/ */
getPrevOverlay(loopOver = false) { private getPrevOverlay(loopOver = false): number {
const prev = this.activeOverlayIndex - 1; const prev = this.activeOverlayIndex - 1;
if (prev < 0) { if (prev < 0) {
@ -87,18 +108,18 @@ export class SearchStore {
} }
@autobind() @autobind()
setNextOverlayActive() { public setNextOverlayActive(): void {
this.activeOverlayIndex = this.getNextOverlay(true); this.activeOverlayIndex = this.getNextOverlay(true);
} }
@autobind() @autobind()
setPrevOverlayActive() { public setPrevOverlayActive(): void {
this.activeOverlayIndex = this.getPrevOverlay(true); this.activeOverlayIndex = this.getPrevOverlay(true);
} }
/** /**
* Gets line index of where active overlay is located * Gets line index of where active overlay is located
* @returns {number} A line index within the text/logs array * @returns A line index within the text/logs array
*/ */
@computed get activeOverlayLine(): number { @computed get activeOverlayLine(): number {
return this.occurrences[this.activeOverlayIndex]; return this.occurrences[this.activeOverlayIndex];
@ -115,25 +136,17 @@ export class SearchStore {
/** /**
* Checks if overlay is active (to highlight it with orange background usually) * Checks if overlay is active (to highlight it with orange background usually)
* @param line Index of the line where overlay is located * @param line Index of the line where overlay is located
* @param occurence Number of the overlay within one line * @param occurrence Number of the overlay within one line
*/ */
@autobind() @autobind()
isActiveOverlay(line: number, occurence: number) { public isActiveOverlay(line: number, occurrence: number): boolean {
const firstLineIndex = this.occurrences.findIndex(item => item === line); const firstLineIndex = this.occurrences.findIndex(item => item === line);
return firstLineIndex + occurence === this.activeOverlayIndex; return firstLineIndex + occurrence === this.activeOverlayIndex;
}
/**
* An utility methods escaping user string to safely pass it into new Regex(variable)
* @param value Unescaped string
*/
escapeRegex(value: string) {
return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
} }
@action @action
reset() { private reset(): void {
this.searchQuery = ""; this.searchQuery = "";
this.activeOverlayIndex = -1; this.activeOverlayIndex = -1;
this.occurrences = []; this.occurrences = [];

View File

@ -8,7 +8,7 @@ import { action, computed, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Align, ListOnScrollProps } from "react-window"; import { Align, ListOnScrollProps } from "react-window";
import { searchStore } from "../../../common/search-store"; import { SearchStore, searchStore } from "../../../common/search-store";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { Button } from "../button"; import { Button } from "../button";
import { Icon } from "../icon"; import { Icon } from "../icon";
@ -164,7 +164,7 @@ export class LogList extends React.Component<Props> {
if (searchQuery) { // If search is enabled, replace keyword with backgrounded <span> if (searchQuery) { // If search is enabled, replace keyword with backgrounded <span>
// Case-insensitive search (lowercasing query and keywords in line) // Case-insensitive search (lowercasing query and keywords in line)
const regex = new RegExp(searchStore.escapeRegex(searchQuery), "gi"); const regex = new RegExp(SearchStore.escapeRegex(searchQuery), "gi");
const matches = item.matchAll(regex); const matches = item.matchAll(regex);
const modified = item.replace(regex, match => match.toLowerCase()); const modified = item.replace(regex, match => match.toLowerCase());
// Splitting text line by keyword // Splitting text line by keyword

View File

@ -59,9 +59,9 @@ export class LogStore {
}; };
/** /**
* Function is used to refreser/stream-like requests. * Function is used to refresher/stream-like requests.
* It changes 'sinceTime' param each time allowing to fetch logs * It changes 'sinceTime' param each time allowing to fetch logs
* starting from last line recieved. * starting from last line received.
* @param tabId * @param tabId
*/ */
loadMore = async (tabId: TabId) => { loadMore = async (tabId: TabId) => {
@ -91,7 +91,7 @@ export class LogStore {
return podsApi.getLogs({ namespace, name }, { return podsApi.getLogs({ namespace, name }, {
...params, ...params,
timestamps: true, // Always setting timestampt to separate old logs from new ones timestamps: true, // Always setting timestamp to separate old logs from new ones
container: selectedContainer.name, container: selectedContainer.name,
previous previous
}).then(result => { }).then(result => {
@ -120,11 +120,7 @@ export class LogStore {
* Returns logs with timestamps for selected tab * Returns logs with timestamps for selected tab
*/ */
get logs() { get logs() {
const id = dockStore.selectedTabId; return this.podLogs.get(dockStore.selectedTabId) ?? [];
if (!this.podLogs.has(id)) return [];
return this.podLogs.get(id);
} }
/** /**