Commit a2ffd9f6 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ak/bubble-up-log-source' into 'master'

Pass log source to the frontend

See merge request gitlab-org/gitlab!22694
parents eac596f7 6869a1ff
---
title: Pass log source to the frontend
merge_request: 22694
author:
type: changed
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { GlDropdown, GlDropdownItem, GlFormGroup, GlSearchBoxByClick } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlFormGroup, GlSearchBoxByClick, GlAlert } from '@gitlab/ui';
import { scrollDown } from '~/lib/utils/scroll_utils'; import { scrollDown } from '~/lib/utils/scroll_utils';
import LogControlButtons from './log_control_buttons.vue'; import LogControlButtons from './log_control_buttons.vue';
export default { export default {
components: { components: {
GlAlert,
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlFormGroup, GlFormGroup,
...@@ -32,14 +33,19 @@ export default { ...@@ -32,14 +33,19 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
clusterApplicationsDocumentationPath: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
searchQuery: '', searchQuery: '',
isElasticStackCalloutDismissed: false,
}; };
}, },
computed: { computed: {
...mapState('environmentLogs', ['environments', 'logs', 'pods']), ...mapState('environmentLogs', ['environments', 'logs', 'pods', 'enableAdvancedQuerying']),
...mapGetters('environmentLogs', ['trace']), ...mapGetters('environmentLogs', ['trace']),
showLoader() { showLoader() {
return this.logs.isLoading || !this.logs.isComplete; return this.logs.isLoading || !this.logs.isComplete;
...@@ -47,6 +53,16 @@ export default { ...@@ -47,6 +53,16 @@ export default {
featureElasticEnabled() { featureElasticEnabled() {
return gon.features && gon.features.enableClusterApplicationElasticStack; return gon.features && gon.features.enableClusterApplicationElasticStack;
}, },
advancedFeaturesEnabled() {
return this.featureElasticEnabled && this.enableAdvancedQuerying;
},
shouldShowElasticStackCallout() {
return (
!this.isElasticStackCalloutDismissed &&
!this.environments.isLoading &&
!this.advancedFeaturesEnabled
);
},
}, },
watch: { watch: {
trace(val) { trace(val) {
...@@ -80,6 +96,22 @@ export default { ...@@ -80,6 +96,22 @@ export default {
</script> </script>
<template> <template>
<div class="build-page-pod-logs mt-3"> <div class="build-page-pod-logs mt-3">
<gl-alert
v-if="shouldShowElasticStackCallout"
class="mb-3"
@dismiss="isElasticStackCalloutDismissed = true"
>
{{
s__(
'Environments|Install Elastic Stack on your cluster to enable advanced querying capabilities such as full text search.',
)
}}
<a :href="clusterApplicationsDocumentationPath">
<strong>
{{ s__('View Documentation') }}
</strong>
</a>
</gl-alert>
<div class="top-bar js-top-bar d-flex"> <div class="top-bar js-top-bar d-flex">
<div class="row"> <div class="row">
<gl-form-group <gl-form-group
...@@ -138,7 +170,7 @@ export default { ...@@ -138,7 +170,7 @@ export default {
> >
<gl-search-box-by-click <gl-search-box-by-click
v-model.trim="searchQuery" v-model.trim="searchQuery"
:disabled="environments.isLoading" :disabled="environments.isLoading || !advancedFeaturesEnabled"
:placeholder="s__('Environments|Search')" :placeholder="s__('Environments|Search')"
class="js-logs-search" class="js-logs-search"
type="search" type="search"
......
...@@ -71,7 +71,8 @@ export const fetchLogs = ({ commit, state }) => { ...@@ -71,7 +71,8 @@ export const fetchLogs = ({ commit, state }) => {
return requestLogsUntilData(params) return requestLogsUntilData(params)
.then(({ data }) => { .then(({ data }) => {
const { pod_name, pods, logs } = data; const { pod_name, pods, logs, enable_advanced_querying } = data;
commit(types.ENABLE_ADVANCED_QUERYING, enable_advanced_querying);
commit(types.SET_CURRENT_POD_NAME, pod_name); commit(types.SET_CURRENT_POD_NAME, pod_name);
commit(types.RECEIVE_PODS_DATA_SUCCESS, pods); commit(types.RECEIVE_PODS_DATA_SUCCESS, pods);
......
export const SET_PROJECT_PATH = 'SET_PROJECT_PATH'; export const SET_PROJECT_PATH = 'SET_PROJECT_PATH';
export const SET_PROJECT_ENVIRONMENT = 'SET_PROJECT_ENVIRONMENT'; export const SET_PROJECT_ENVIRONMENT = 'SET_PROJECT_ENVIRONMENT';
export const SET_SEARCH = 'SET_SEARCH'; export const SET_SEARCH = 'SET_SEARCH';
export const ENABLE_ADVANCED_QUERYING = 'ENABLE_ADVANCED_QUERYING';
export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA'; export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA';
export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS'; export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS';
......
...@@ -11,6 +11,11 @@ export default { ...@@ -11,6 +11,11 @@ export default {
state.search = searchQuery; state.search = searchQuery;
}, },
/** Log source supports advanced features */
[types.ENABLE_ADVANCED_QUERYING](state, enableAdvancedQuerying) {
state.enableAdvancedQuerying = enableAdvancedQuerying;
},
/** Environments data */ /** Environments data */
[types.SET_PROJECT_ENVIRONMENT](state, environmentName) { [types.SET_PROJECT_ENVIRONMENT](state, environmentName) {
state.environments.current = environmentName; state.environments.current = environmentName;
......
...@@ -9,6 +9,11 @@ export default () => ({ ...@@ -9,6 +9,11 @@ export default () => ({
*/ */
search: '', search: '',
/**
* True if log source is elasticsearch
*/
enableAdvancedQuerying: false,
/** /**
* Environments list information * Environments list information
*/ */
......
...@@ -35,7 +35,8 @@ module EE ...@@ -35,7 +35,8 @@ module EE
"environment-name": environment.name, "environment-name": environment.name,
"environments-path": project_environments_path(project, format: :json), "environments-path": project_environments_path(project, format: :json),
"project-full-path": project.full_path, "project-full-path": project.full_path,
"environment-id": environment.id "environment-id": environment.id,
"cluster-applications-documentation-path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack')
} }
end end
......
...@@ -85,7 +85,8 @@ module EE ...@@ -85,7 +85,8 @@ module EE
private private
def pod_logs(pod_name, namespace, container: nil, search: nil) def pod_logs(pod_name, namespace, container: nil, search: nil)
logs = if ::Feature.enabled?(:enable_cluster_application_elastic_stack) && elastic_stack_client enable_advanced_querying = ::Feature.enabled?(:enable_cluster_application_elastic_stack) && !!elastic_stack_client
logs = if enable_advanced_querying
elastic_stack_pod_logs(namespace, pod_name, container, search) elastic_stack_pod_logs(namespace, pod_name, container, search)
else else
platform_pod_logs(namespace, pod_name, container) platform_pod_logs(namespace, pod_name, container)
...@@ -95,7 +96,8 @@ module EE ...@@ -95,7 +96,8 @@ module EE
logs: logs, logs: logs,
status: :success, status: :success,
pod_name: pod_name, pod_name: pod_name,
container_name: container container_name: container,
enable_advanced_querying: enable_advanced_querying
} }
end end
......
...@@ -9,7 +9,7 @@ class PodLogsService < ::BaseService ...@@ -9,7 +9,7 @@ class PodLogsService < ::BaseService
PARAMS = %w(pod_name container_name search).freeze PARAMS = %w(pod_name container_name search).freeze
SUCCESS_RETURN_KEYS = [:status, :logs, :pod_name, :container_name, :pods].freeze SUCCESS_RETURN_KEYS = [:status, :logs, :pod_name, :container_name, :pods, :enable_advanced_querying].freeze
steps :check_param_lengths, steps :check_param_lengths,
:check_deployment_platform, :check_deployment_platform,
...@@ -83,7 +83,7 @@ class PodLogsService < ::BaseService ...@@ -83,7 +83,7 @@ class PodLogsService < ::BaseService
return { status: :processing } unless response return { status: :processing } unless response
result.merge!(response.slice(:pod_name, :container_name, :logs)) result.merge!(response.slice(:pod_name, :container_name, :logs, :enable_advanced_querying))
if response[:status] == :error if response[:status] == :error
error(response[:error]).reverse_merge(result) error(response[:error]).reverse_merge(result)
......
...@@ -14,6 +14,7 @@ import { ...@@ -14,6 +14,7 @@ import {
mockTrace, mockTrace,
mockPodName, mockPodName,
mockEnvironmentsEndpoint, mockEnvironmentsEndpoint,
mockDocumentationPath,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/lib/utils/scroll_utils'); jest.mock('~/lib/utils/scroll_utils');
...@@ -28,6 +29,7 @@ describe('EnvironmentLogs', () => { ...@@ -28,6 +29,7 @@ describe('EnvironmentLogs', () => {
projectFullPath: mockProjectPath, projectFullPath: mockProjectPath,
environmentName: mockEnvName, environmentName: mockEnvName,
environmentsPath: mockEnvironmentsEndpoint, environmentsPath: mockEnvironmentsEndpoint,
clusterApplicationsDocumentationPath: mockDocumentationPath,
}; };
const actionMocks = { const actionMocks = {
...@@ -168,12 +170,37 @@ describe('EnvironmentLogs', () => { ...@@ -168,12 +170,37 @@ describe('EnvironmentLogs', () => {
}); });
}); });
describe('ES enabled and legacy environment', () => {
beforeEach(() => {
state.pods.options = [];
state.logs.lines = [];
state.logs.isLoading = false;
state.environments.options = [];
state.environments.isLoading = false;
state.enableAdvancedQuerying = false;
gon.features = gon.features || {};
gon.features.enableClusterApplicationElasticStack = true;
initWrapper();
});
it('displays a disabled search bar', () => {
expect(findSearchBar().exists()).toEqual(true);
expect(findSearchBar().attributes('disabled')).toEqual('true');
});
});
describe('state with data', () => { describe('state with data', () => {
beforeEach(() => { beforeEach(() => {
actionMocks.setInitData.mockImplementation(() => { actionMocks.setInitData.mockImplementation(() => {
state.pods.options = mockPods; state.pods.options = mockPods;
state.environments.current = mockEnvName; state.environments.current = mockEnvName;
[state.pods.current] = state.pods.options; [state.pods.current] = state.pods.options;
state.enableAdvancedQuerying = true;
state.logs.isComplete = false; state.logs.isComplete = false;
state.logs.lines = mockLogsResult; state.logs.lines = mockLogsResult;
...@@ -233,7 +260,7 @@ describe('EnvironmentLogs', () => { ...@@ -233,7 +260,7 @@ describe('EnvironmentLogs', () => {
expect(trace.text().split('\n')).toEqual(mockTrace); expect(trace.text().split('\n')).toEqual(mockTrace);
}); });
it('displays the search bar', () => { it('displays an enabled search bar', () => {
expect(findSearchBar().exists()).toEqual(true); expect(findSearchBar().exists()).toEqual(true);
expect(findSearchBar().attributes('disabled')).toEqual(undefined); expect(findSearchBar().attributes('disabled')).toEqual(undefined);
expect(wrapper.find('#search-fg').attributes('class')).toEqual('col-4'); expect(wrapper.find('#search-fg').attributes('class')).toEqual('col-4');
......
...@@ -2,6 +2,8 @@ export const mockProjectPath = 'root/autodevops-deploy'; ...@@ -2,6 +2,8 @@ export const mockProjectPath = 'root/autodevops-deploy';
export const mockEnvName = 'production'; export const mockEnvName = 'production';
export const mockEnvironmentsEndpoint = `${mockProjectPath}/environments.json`; export const mockEnvironmentsEndpoint = `${mockProjectPath}/environments.json`;
export const mockEnvId = '99'; export const mockEnvId = '99';
export const mockEnableAdvancedQuerying = true;
export const mockDocumentationPath = '/documentation.md';
const makeMockEnvironment = (id, name) => ({ const makeMockEnvironment = (id, name) => ({
id, id,
......
...@@ -23,6 +23,7 @@ import { ...@@ -23,6 +23,7 @@ import {
mockLogsResult, mockLogsResult,
mockEnvName, mockEnvName,
mockSearch, mockSearch,
mockEnableAdvancedQuerying,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -143,6 +144,7 @@ describe('Logs Store actions', () => { ...@@ -143,6 +144,7 @@ describe('Logs Store actions', () => {
pod_name: mockPodName, pod_name: mockPodName,
pods: mockPods, pods: mockPods,
logs: mockLogsResult, logs: mockLogsResult,
enable_advanced_querying: mockEnableAdvancedQuerying,
}); });
mock.onGet(endpoint).replyOnce(202); // mock reactive cache mock.onGet(endpoint).replyOnce(202); // mock reactive cache
...@@ -154,6 +156,7 @@ describe('Logs Store actions', () => { ...@@ -154,6 +156,7 @@ describe('Logs Store actions', () => {
[ [
{ type: types.REQUEST_PODS_DATA }, { type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA }, { type: types.REQUEST_LOGS_DATA },
{ type: types.ENABLE_ADVANCED_QUERYING, payload: mockEnableAdvancedQuerying },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName }, { type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods }, { type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
{ type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult }, { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult },
...@@ -180,6 +183,7 @@ describe('Logs Store actions', () => { ...@@ -180,6 +183,7 @@ describe('Logs Store actions', () => {
pod_name: mockPodName, pod_name: mockPodName,
pods: mockPods, pods: mockPods,
logs: mockLogsResult, logs: mockLogsResult,
enable_advanced_querying: mockEnableAdvancedQuerying,
}); });
mock.onGet(endpoint).replyOnce(202); // mock reactive cache mock.onGet(endpoint).replyOnce(202); // mock reactive cache
...@@ -191,6 +195,7 @@ describe('Logs Store actions', () => { ...@@ -191,6 +195,7 @@ describe('Logs Store actions', () => {
[ [
{ type: types.REQUEST_PODS_DATA }, { type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA }, { type: types.REQUEST_LOGS_DATA },
{ type: types.ENABLE_ADVANCED_QUERYING, payload: mockEnableAdvancedQuerying },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName }, { type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods }, { type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
{ type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult }, { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult },
...@@ -210,6 +215,7 @@ describe('Logs Store actions', () => { ...@@ -210,6 +215,7 @@ describe('Logs Store actions', () => {
pod_name: mockPodName, pod_name: mockPodName,
pods: mockPods, pods: mockPods,
logs: mockLogsResult, logs: mockLogsResult,
enable_advanced_querying: mockEnableAdvancedQuerying,
}); });
mock.onGet(endpoint).replyOnce(202); // mock reactive cache mock.onGet(endpoint).replyOnce(202); // mock reactive cache
...@@ -220,6 +226,7 @@ describe('Logs Store actions', () => { ...@@ -220,6 +226,7 @@ describe('Logs Store actions', () => {
[ [
{ type: types.REQUEST_PODS_DATA }, { type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA }, { type: types.REQUEST_LOGS_DATA },
{ type: types.ENABLE_ADVANCED_QUERYING, payload: mockEnableAdvancedQuerying },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName }, { type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods }, { type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
{ type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult }, { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult },
......
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
mockPodName, mockPodName,
mockLogsResult, mockLogsResult,
mockSearch, mockSearch,
mockEnableAdvancedQuerying,
} from '../mock_data'; } from '../mock_data';
describe('Logs Store Mutations', () => { describe('Logs Store Mutations', () => {
...@@ -124,6 +125,17 @@ describe('Logs Store Mutations', () => { ...@@ -124,6 +125,17 @@ describe('Logs Store Mutations', () => {
expect(state.pods.current).toEqual(mockPodName); expect(state.pods.current).toEqual(mockPodName);
}); });
}); });
describe('ENABLE_ADVANCED_QUERYING', () => {
it('set advanced querying toggle', () => {
state.enableAdvancedQuerying = !mockEnableAdvancedQuerying;
mutations[types.ENABLE_ADVANCED_QUERYING](state, mockEnableAdvancedQuerying);
expect(state.enableAdvancedQuerying).toEqual(mockEnableAdvancedQuerying);
});
});
describe('REQUEST_PODS_DATA', () => { describe('REQUEST_PODS_DATA', () => {
it('receives log data error and stops loading', () => { it('receives log data error and stops loading', () => {
mutations[types.REQUEST_PODS_DATA](state); mutations[types.REQUEST_PODS_DATA](state);
......
...@@ -141,6 +141,7 @@ describe Clusters::Platforms::Kubernetes do ...@@ -141,6 +141,7 @@ describe Clusters::Platforms::Kubernetes do
let(:pod_name) { 'pod-1' } let(:pod_name) { 'pod-1' }
let(:namespace) { 'app' } let(:namespace) { 'app' }
let(:container) { 'some-container' } let(:container) { 'some-container' }
let(:enable_advanced_querying) { false }
let(:expected_logs) do let(:expected_logs) do
[ [
{ message: "Log 1", timestamp: "2019-12-13T14:04:22.123456Z" }, { message: "Log 1", timestamp: "2019-12-13T14:04:22.123456Z" },
...@@ -157,6 +158,7 @@ describe Clusters::Platforms::Kubernetes do ...@@ -157,6 +158,7 @@ describe Clusters::Platforms::Kubernetes do
expect(subject[:status]).to eq(:success) expect(subject[:status]).to eq(:success)
expect(subject[:pod_name]).to eq(pod_name) expect(subject[:pod_name]).to eq(pod_name)
expect(subject[:container_name]).to eq(container) expect(subject[:container_name]).to eq(container)
expect(subject[:enable_advanced_querying]).to eq(enable_advanced_querying)
end end
end end
...@@ -175,6 +177,7 @@ describe Clusters::Platforms::Kubernetes do ...@@ -175,6 +177,7 @@ describe Clusters::Platforms::Kubernetes do
context 'when ElasticSearch is enabled' do context 'when ElasticSearch is enabled' do
let(:cluster) { create(:cluster, :project, platform_kubernetes: service) } let(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) } let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) }
let(:enable_advanced_querying) { true }
before do before do
expect_any_instance_of(::Clusters::Applications::ElasticStack).to receive(:elasticsearch_client).at_least(:once).and_return(Elasticsearch::Transport::Client.new) expect_any_instance_of(::Clusters::Applications::ElasticStack).to receive(:elasticsearch_client).at_least(:once).and_return(Elasticsearch::Transport::Client.new)
......
...@@ -14,6 +14,7 @@ describe PodLogsService do ...@@ -14,6 +14,7 @@ describe PodLogsService do
let(:pods) { [pod_name] } let(:pods) { [pod_name] }
let(:container_name) { 'container-1' } let(:container_name) { 'container-1' }
let(:search) { nil } let(:search) { nil }
let(:enable_advanced_querying) { false }
let(:logs) { ['Log 1', 'Log 2', 'Log 3'] } let(:logs) { ['Log 1', 'Log 2', 'Log 3'] }
let(:result) { subject.execute } let(:result) { subject.execute }
...@@ -36,6 +37,7 @@ describe PodLogsService do ...@@ -36,6 +37,7 @@ describe PodLogsService do
expect(result[:pods]).to eq(pods) expect(result[:pods]).to eq(pods)
expect(result[:pod_name]).to eq(response_pod_name) expect(result[:pod_name]).to eq(response_pod_name)
expect(result[:container_name]).to eq(container_name) expect(result[:container_name]).to eq(container_name)
expect(result[:enable_advanced_querying]).to eq(enable_advanced_querying)
end end
end end
...@@ -61,7 +63,8 @@ describe PodLogsService do ...@@ -61,7 +63,8 @@ describe PodLogsService do
status: :error, status: :error,
error: message, error: message,
pod_name: response_pod_name, pod_name: response_pod_name,
container_name: container_name container_name: container_name,
enable_advanced_querying: enable_advanced_querying
}) })
end end
end end
...@@ -74,7 +77,8 @@ describe PodLogsService do ...@@ -74,7 +77,8 @@ describe PodLogsService do
status: :success, status: :success,
logs: ["Log 1", "Log 2", "Log 3"], logs: ["Log 1", "Log 2", "Log 3"],
pod_name: response_pod_name, pod_name: response_pod_name,
container_name: container_name container_name: container_name,
enable_advanced_querying: enable_advanced_querying
}) })
end end
end end
......
...@@ -6999,6 +6999,9 @@ msgstr "" ...@@ -6999,6 +6999,9 @@ msgstr ""
msgid "Environments|Environments are places where code gets deployed, such as staging or production." msgid "Environments|Environments are places where code gets deployed, such as staging or production."
msgstr "" msgstr ""
msgid "Environments|Install Elastic Stack on your cluster to enable advanced querying capabilities such as full text search."
msgstr ""
msgid "Environments|Job" msgid "Environments|Job"
msgstr "" msgstr ""
...@@ -20367,6 +20370,9 @@ msgstr "" ...@@ -20367,6 +20370,9 @@ msgstr ""
msgid "Very helpful" msgid "Very helpful"
msgstr "" msgstr ""
msgid "View Documentation"
msgstr ""
msgid "View app" msgid "View app"
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment