Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log search #1114

Merged
merged 46 commits into from Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
edf59c9
Moving logs to virtual list
aleksfront Oct 20, 2020
7234d2e
Introducing log search
aleksfront Oct 20, 2020
118ed20
Setting ref for VirtualList to access its methods
aleksfront Oct 21, 2020
5c084b8
Introducing search store
aleksfront Oct 21, 2020
f8548d6
Centering overlay when scroll to it
aleksfront Oct 21, 2020
93ae4b6
Using SearchInput in PodLogSearch
aleksfront Oct 21, 2020
7ac13f5
Using Prev/Next icons for search
aleksfront Oct 21, 2020
14e88a5
No trigger logs load when scrolled by method
aleksfront Oct 22, 2020
de33f52
SearchInput refactoring
aleksfront Oct 22, 2020
cfb6c3c
Adding find counters
aleksfront Oct 22, 2020
45b6faf
Clean search query on dock tab change
aleksfront Oct 22, 2020
a6eb283
Refresh search when logs get changed
aleksfront Oct 22, 2020
e3a3fb4
Case-insensitive search
aleksfront Oct 22, 2020
01e485d
Improve logs scrolling experience
aleksfront Oct 22, 2020
6bb874d
Catching empty logs in various places
aleksfront Oct 22, 2020
4b0b635
Fixing downloading logs
aleksfront Oct 22, 2020
d5ed79d
Clean up some duplicated styles
aleksfront Oct 22, 2020
c099711
Removing jump-to-bottom animation
aleksfront Oct 22, 2020
c962ea6
Fixing since label
aleksfront Oct 22, 2020
99b6bab
Reducing container selector size
aleksfront Oct 22, 2020
fcf9422
Scroll down to bottom after each reload
aleksfront Oct 23, 2020
de536b8
Fix search within timestamps if they not provided
aleksfront Oct 23, 2020
d736f24
Use log row hover color from theme
aleksfront Oct 23, 2020
2e2eec7
Add search bindings for 'Esc' & 'Enter' hits
aleksfront Oct 26, 2020
b0c0794
Merge branch 'master' into logs-search
aleksfront Oct 30, 2020
61a2936
Merge branch 'master' into logs-search
aleksfront Oct 30, 2020
bc8a688
Focus input fields on CmdOrCtrl+F
aleksfront Oct 30, 2020
16cde00
Move search.store.ts in to /common folder
aleksfront Nov 1, 2020
d262c76
search.store.ts -> search-store.ts
aleksfront Nov 1, 2020
4ec29ce
Adding test for search store
aleksfront Nov 1, 2020
a054ae2
Adding integration tests for logs
aleksfront Nov 2, 2020
139c114
Fixing scroll jumping bug
aleksfront Nov 2, 2020
83c760d
Removing download icon check for testing purpose
aleksfront Nov 2, 2020
ea96a7f
Merge branch 'master' into logs-search
aleksfront Nov 2, 2020
858acc1
Removing clicking on nginx-create-pod-test
aleksfront Nov 3, 2020
94ed372
Moving log tests before cluster operations
aleksfront Nov 3, 2020
da12885
Build extensions before integration tests
nevalla Nov 3, 2020
cc3af77
Build also npm before integration tests
nevalla Nov 3, 2020
6283daa
Move npm build and extension build into own build step
nevalla Nov 3, 2020
4e1b901
Merge branch 'master' into logs-search
aleksfront Nov 4, 2020
cc8902c
Removing separator sketches
aleksfront Nov 4, 2020
c3c2e52
Horizontal scrolling to founded keyword
aleksfront Nov 5, 2020
4caddf5
Merge branch 'master' into logs-search
aleksfront Nov 5, 2020
871173e
Delaying horizontal scrolling
aleksfront Nov 5, 2020
b67d367
Merge branch 'master' into logs-search
aleksfront Nov 9, 2020
eb4435e
Merge branch 'master' into logs-search
aleksfront Nov 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/renderer/components/+apps-helm-charts/helm-charts.tsx
Expand Up @@ -11,7 +11,7 @@ import { navigation } from "../../navigation";
import { ItemListLayout } from "../item-object-list/item-list-layout";
import { t, Trans } from "@lingui/macro";
import { _i18n } from "../../i18n";
import { SearchInput } from "../input";
import { SearchInputUrl } from "../input";

enum sortBy {
name = "name",
Expand Down Expand Up @@ -72,7 +72,7 @@ export class HelmCharts extends Component<Props> {
(items: HelmChart[]) => items.filter(item => !item.deprecated)
]}
customizeHeader={() => (
<SearchInput placeholder={_i18n._(t`Search Helm Charts`)} />
<SearchInputUrl placeholder={_i18n._(t`Search Helm Charts`)} />
)}
renderTableHeader={[
{ className: "icon" },
Expand Down
18 changes: 11 additions & 7 deletions src/renderer/components/dock/pod-log-controls.tsx
Expand Up @@ -8,22 +8,26 @@ import { Icon } from "../icon";
import { _i18n } from "../../i18n";
import { cssNames, downloadFile } from "../../utils";
import { Pod } from "../../api/endpoints";
import { PodLogSearch, PodLogSearchProps } from "./pod-log-search";

interface Props {
interface Props extends PodLogSearchProps {
ready: boolean
tabId: string
tabData: IPodLogsData
logs: string[][]
logs: string[]
save: (data: Partial<IPodLogsData>) => void
reload: () => void
onSearch: (query: string) => void
}

export const PodLogControls = observer((props: Props) => {
if (!props.ready) return null;
const { tabData, tabId, save, reload, logs } = props;
const { tabData, save, reload, tabId, logs } = props;
const { selectedContainer, showTimestamps, previous } = tabData;
const since = podLogsStore.getTimestamps(podLogsStore.logs.get(tabId)[0]);
const rawLogs = podLogsStore.logs.get(tabId);
const since = rawLogs.length ? podLogsStore.getTimestamps(rawLogs[0]) : null;
const pod = new Pod(tabData.pod);

const toggleTimestamps = () => {
save({ showTimestamps: !showTimestamps });
}
Expand All @@ -35,8 +39,7 @@ export const PodLogControls = observer((props: Props) => {

const downloadLogs = () => {
const fileName = selectedContainer ? selectedContainer.name : pod.getName();
const [oldLogs, newLogs] = logs;
downloadFile(fileName + ".log", [...oldLogs, ...newLogs].join("\n"), "text/plain");
downloadFile(fileName + ".log", logs.join("\n"), "text/plain");
}

const onContainerChange = (option: SelectOption) => {
Expand Down Expand Up @@ -92,7 +95,7 @@ export const PodLogControls = observer((props: Props) => {
</>
)}
</div>
<div className="flex gaps">
<div className="flex box grow gaps align-center">
<Icon
material="av_timer"
onClick={toggleTimestamps}
Expand All @@ -110,6 +113,7 @@ export const PodLogControls = observer((props: Props) => {
onClick={downloadLogs}
tooltip={_i18n._(t`Save`)}
/>
<PodLogSearch {...props} />
</div>
</div>
);
Expand Down
10 changes: 10 additions & 0 deletions src/renderer/components/dock/pod-log-search.scss
@@ -0,0 +1,10 @@
.PodLogsSearch {
.SearchInput {
min-width: 150px;
width: 150px;

.find-count {
margin-left: 2px;
}
}
}
70 changes: 70 additions & 0 deletions src/renderer/components/dock/pod-log-search.tsx
@@ -0,0 +1,70 @@
import "./pod-log-search.scss";

import React from "react";
import { observer } from "mobx-react";
import { SearchInput } from "../input";
import { searchStore } from "./search.store";
import { Icon } from "../icon";
import { _i18n } from "../../i18n";
import { t } from "@lingui/macro";

export interface PodLogSearchProps {
onSearch: (query: string) => void
toPrevOverlay: () => void
toNextOverlay: () => void
logs: string[]
}

export const PodLogSearch = observer((props: PodLogSearchProps) => {
const { logs, onSearch, toPrevOverlay, toNextOverlay } = props;
const { setNextOverlayActive, setPrevOverlayActive, searchQuery, occurrences, activeFind, totalFinds } = searchStore;
const jumpDisabled = !searchQuery || !occurrences.length;
const findCounts = (
<div className="find-count">
{activeFind}/{totalFinds}
</div>
);

const setSearch = (query: string) => {
searchStore.onSearch(logs, query);
onSearch(query);
};

const onPrevOverlay = () => {
setPrevOverlayActive();
toPrevOverlay();
}

const onNextOverlay = () => {
setNextOverlayActive();
toNextOverlay();
}

return (
<div className="PodLogsSearch flex box grow justify-flex-end gaps align-center">
<SearchInput
value={searchQuery}
onChange={setSearch}
closeIcon={false}
contentRight={totalFinds > 0 && findCounts}
/>
<Icon
material="keyboard_arrow_up"
tooltip={_i18n._(t`Previous`)}
onClick={onPrevOverlay}
disabled={jumpDisabled}
/>
<Icon
material="keyboard_arrow_down"
tooltip={_i18n._(t`Next`)}
onClick={onNextOverlay}
disabled={jumpDisabled}
/>
<Icon
material="close"
tooltip={_i18n._(t`Clear`)}
onClick={() => setSearch("")}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it doesn't work keeping it inside the SearchInput. It might fit better to the left of the up/down arrows if not in the SearchInput?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I follow the usual rules for placing elements in the search, such as native search in the browser.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I didn't make the connection because the input field is delineated by a rectangle whereas the native search in the browser delineates the whole search tool by a rectangle, and separates the input field from the "prev next clear" buttons with just a vertical line

</div>
);
});
55 changes: 45 additions & 10 deletions src/renderer/components/dock/pod-logs.scss
Expand Up @@ -6,19 +6,47 @@
// `overflow: overlay` don't allow scroll to the last line
overflow: auto;

position: relative;
color: $textColorAccent;
background: $logsBackground;
line-height: var(--log-line-height);
border-radius: 2px;
padding: $padding * 2;
font-family: $font-monospace;
font-size: smaller;
white-space: pre;
flex-grow: 1;

> div {
// Provides font better readability on large screens
-webkit-font-smoothing: subpixel-antialiased;
.find-overlay {
position: absolute;
border-radius: 2px;
background-color: #8cc474;
margin-top: 4px;
opacity: 0.5;
}

.VirtualList {
height: 100%;

.list {
.LogRow {
padding: 2px 16px;
height: 18px; // Must be equal to lineHeight variable in pod-logs.scss
font-family: $font-monospace;
font-size: smaller;
white-space: pre;
-webkit-font-smoothing: auto; // Better readability on non-retina screens

&:hover {
// TODO: Use a theme var to styling
background: #35373a;
}

span {
border-radius: 2px;
background-color: #8cc474b8;
-webkit-font-smoothing: auto;

&.active {
background-color: orange;
}
}
}
}
}
}

Expand Down Expand Up @@ -47,7 +75,8 @@
padding: $unit / 2 $unit * 1.5;
border-radius: $unit * 2;
opacity: 0;
transition: opacity 0.2s;
z-index: 2;
top: 20px;

&.active {
opacity: 1;
Expand All @@ -57,4 +86,10 @@
--size: $unit * 2;
}
}

.PodLogControls {
.Select {
min-width: 150px;
}
}
}
14 changes: 11 additions & 3 deletions src/renderer/components/dock/pod-logs.store.ts
Expand Up @@ -5,7 +5,7 @@ import { DockTabStore } from "./dock-tab.store";
import { dockStore, IDockTab, TabKind } from "./dock.store";
import { t } from "@lingui/macro";
import { _i18n } from "../../i18n";
import { isDevelopment } from "../../../common/vars";
import { searchStore } from "./search.store";

export interface IPodLogsData {
pod: Pod;
Expand All @@ -20,7 +20,7 @@ type TabId = string;
type PodLogLine = string;

// Number for log lines to load
export const logRange = isDevelopment ? 100 : 1000;
export const logRange = 500;

@autobind()
export class PodLogsStore extends DockTabStore<IPodLogsData> {
Expand Down Expand Up @@ -48,6 +48,13 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {

reaction(() => this.logs.get(dockStore.selectedTabId), () => {
this.setNewLogSince(dockStore.selectedTabId);
// Refresh search when logs changed
searchStore.onSearch(this.logs.get(dockStore.selectedTabId));
})

reaction(() => dockStore.selectedTabId, () => {
// Clear search query on tab change
searchStore.reset();
})
}

Expand Down Expand Up @@ -82,6 +89,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
* @param tabId
*/
loadMore = async (tabId: TabId) => {
if (!this.logs.get(tabId).length) return;
const oldLogs = this.logs.get(tabId);
const logs = await this.loadLogs(tabId, {
sinceTime: this.getLastSinceTime(tabId)
Expand Down Expand Up @@ -120,7 +128,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
* @param tabId
*/
setNewLogSince(tabId: TabId) {
if (!this.logs.has(tabId) || this.newLogSince.has(tabId)) return;
if (!this.logs.has(tabId) || !this.logs.get(tabId).length || this.newLogSince.has(tabId)) return;
const timestamp = this.getLastSinceTime(tabId);
this.newLogSince.set(tabId, timestamp.split(".")[0]); // Removing milliseconds from string
}
Expand Down