Commit ccf299df authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch 'jivanvl-add-managed-apps-environments-dropdown' into 'master'

Add managed-apps section in log explorer

See merge request gitlab-org/gitlab!36769
parents 72d09764 a3615d91
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
GlDropdown, GlDropdown,
GlDropdownHeader, GlDropdownHeader,
GlDropdownItem, GlDropdownItem,
GlDropdownDivider,
GlInfiniteScroll, GlInfiniteScroll,
} from '@gitlab/ui'; } from '@gitlab/ui';
...@@ -27,6 +28,7 @@ export default { ...@@ -27,6 +28,7 @@ export default {
GlDropdown, GlDropdown,
GlDropdownHeader, GlDropdownHeader,
GlDropdownItem, GlDropdownItem,
GlDropdownDivider,
GlInfiniteScroll, GlInfiniteScroll,
LogSimpleFilters, LogSimpleFilters,
LogAdvancedFilters, LogAdvancedFilters,
...@@ -55,6 +57,10 @@ export default { ...@@ -55,6 +57,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
clustersPath: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -63,7 +69,7 @@ export default { ...@@ -63,7 +69,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods']), ...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods', 'managedApps']),
...mapGetters('environmentLogs', ['trace', 'showAdvancedFilters']), ...mapGetters('environmentLogs', ['trace', 'showAdvancedFilters']),
showLoader() { showLoader() {
...@@ -85,12 +91,15 @@ export default { ...@@ -85,12 +91,15 @@ export default {
}); });
this.fetchEnvironments(this.environmentsPath); this.fetchEnvironments(this.environmentsPath);
this.fetchManagedApps(this.clustersPath);
}, },
methods: { methods: {
...mapActions('environmentLogs', [ ...mapActions('environmentLogs', [
'setInitData', 'setInitData',
'showEnvironment', 'showEnvironment',
'showManagedApp',
'fetchEnvironments', 'fetchEnvironments',
'fetchManagedApps',
'refreshPodLogs', 'refreshPodLogs',
'fetchMoreLogsPrepend', 'fetchMoreLogsPrepend',
'dismissRequestEnvironmentsError', 'dismissRequestEnvironmentsError',
...@@ -101,6 +110,9 @@ export default { ...@@ -101,6 +110,9 @@ export default {
isCurrentEnvironment(envName) { isCurrentEnvironment(envName) {
return envName === this.environments.current; return envName === this.environments.current;
}, },
isCurrentManagedApp(appName) {
return appName === this.managedApps.current;
},
topReached() { topReached() {
if (!this.logs.isLoading) { if (!this.logs.isLoading) {
this.fetchMoreLogsPrepend(); this.fetchMoreLogsPrepend();
...@@ -164,12 +176,12 @@ export default { ...@@ -164,12 +176,12 @@ export default {
<div class="flex-grow-0"> <div class="flex-grow-0">
<gl-dropdown <gl-dropdown
id="environments-dropdown" id="environments-dropdown"
:text="environments.current" :text="environments.current || managedApps.current"
:disabled="environments.isLoading" :disabled="environments.isLoading"
class="mb-2 gl-h-32 pr-2 d-flex d-md-block js-environments-dropdown" class="mb-2 gl-h-32 pr-2 d-flex d-md-block js-environments-dropdown"
> >
<gl-dropdown-header class="text-center"> <gl-dropdown-header class="gl-text-center">
{{ s__('Environments|Select environment') }} {{ s__('Environments|Environments') }}
</gl-dropdown-header> </gl-dropdown-header>
<gl-dropdown-item <gl-dropdown-item
v-for="env in environments.options" v-for="env in environments.options"
...@@ -181,7 +193,24 @@ export default { ...@@ -181,7 +193,24 @@ export default {
:class="{ invisible: !isCurrentEnvironment(env.name) }" :class="{ invisible: !isCurrentEnvironment(env.name) }"
name="status_success_borderless" name="status_success_borderless"
/> />
<div class="flex-grow-1">{{ env.name }}</div> <div class="gl-flex-grow-1">{{ env.name }}</div>
</div>
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-header class="gl-text-center">
{{ s__('Environments|Managed apps') }}
</gl-dropdown-header>
<gl-dropdown-item
v-for="app in managedApps.options"
:key="app.id"
@click="showManagedApp(app.name)"
>
<div class="gl-display-flex">
<gl-icon
:class="{ invisible: !isCurrentManagedApp(app.name) }"
name="status_success_borderless"
/>
<div class="gl-flex-grow-1">{{ app.name }}</div>
</div> </div>
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
......
...@@ -8,4 +8,10 @@ export const tracking = { ...@@ -8,4 +8,10 @@ export const tracking = {
TIME_RANGE_SET: 'time_range_set', TIME_RANGE_SET: 'time_range_set',
ENVIRONMENT_SELECTED: 'environment_selected', ENVIRONMENT_SELECTED: 'environment_selected',
REFRESH_POD_LOGS: 'refresh_pod_logs', REFRESH_POD_LOGS: 'refresh_pod_logs',
MANAGED_APP_SELECTED: 'managed_app_selected',
};
export const logExplorerOptions = {
environments: 'environments',
managedApps: 'managedApps',
}; };
...@@ -2,7 +2,7 @@ import { backOff } from '~/lib/utils/common_utils'; ...@@ -2,7 +2,7 @@ import { backOff } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { convertToFixedRange } from '~/lib/utils/datetime_range'; import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { TOKEN_TYPE_POD_NAME, tracking } from '../constants'; import { TOKEN_TYPE_POD_NAME, tracking, logExplorerOptions } from '../constants';
import trackLogs from '../logs_tracking_helper'; import trackLogs from '../logs_tracking_helper';
import * as types from './mutation_types'; import * as types from './mutation_types';
...@@ -25,9 +25,15 @@ const requestUntilData = (url, params) => ...@@ -25,9 +25,15 @@ const requestUntilData = (url, params) =>
const requestLogsUntilData = ({ commit, state }) => { const requestLogsUntilData = ({ commit, state }) => {
const params = {}; const params = {};
const { logs_api_path } = state.environments.options.find( const type = state.environments.current
({ name }) => name === state.environments.current, ? logExplorerOptions.environments
); : logExplorerOptions.managedApps;
const selectedObj = state[type].options.find(({ name }) => name === state[type].current);
const path =
type === logExplorerOptions.environments
? selectedObj.logs_api_path
: selectedObj.gitlab_managed_apps_logs_path;
if (state.pods.current) { if (state.pods.current) {
params.pod_name = state.pods.current; params.pod_name = state.pods.current;
...@@ -48,7 +54,7 @@ const requestLogsUntilData = ({ commit, state }) => { ...@@ -48,7 +54,7 @@ const requestLogsUntilData = ({ commit, state }) => {
params.cursor = state.logs.cursor; params.cursor = state.logs.cursor;
} }
return requestUntilData(logs_api_path, params); return requestUntilData(path, params);
}; };
/** /**
...@@ -100,6 +106,11 @@ export const showEnvironment = ({ dispatch, commit }, environmentName) => { ...@@ -100,6 +106,11 @@ export const showEnvironment = ({ dispatch, commit }, environmentName) => {
dispatch('fetchLogs', tracking.ENVIRONMENT_SELECTED); dispatch('fetchLogs', tracking.ENVIRONMENT_SELECTED);
}; };
export const showManagedApp = ({ dispatch, commit }, managedApp) => {
commit(types.SET_MANAGED_APP, managedApp);
dispatch('fetchLogs', tracking.MANAGED_APP_SELECTED);
};
export const refreshPodLogs = ({ dispatch, commit }) => { export const refreshPodLogs = ({ dispatch, commit }) => {
commit(types.REFRESH_POD_LOGS); commit(types.REFRESH_POD_LOGS);
dispatch('fetchLogs', tracking.REFRESH_POD_LOGS); dispatch('fetchLogs', tracking.REFRESH_POD_LOGS);
...@@ -124,6 +135,23 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => { ...@@ -124,6 +135,23 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
}); });
}; };
/**
* Fetch managed apps data
* @param {Object} store
* @param {String} clustersPath
*/
export const fetchManagedApps = ({ commit }, clustersPath) => {
return axios
.get(clustersPath)
.then(({ data }) => {
commit(types.RECEIVE_MANAGED_APPS_DATA_SUCCESS, data.clusters);
})
.catch(() => {
commit(types.RECEIVE_MANAGED_APPS_DATA_ERROR);
});
};
export const fetchLogs = ({ commit, state }, trackingLabel) => { export const fetchLogs = ({ commit, state }, trackingLabel) => {
commit(types.REQUEST_LOGS_DATA); commit(types.REQUEST_LOGS_DATA);
......
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 SET_MANAGED_APP = 'SET_MANAGED_APP';
export const SET_TIME_RANGE = 'SET_TIME_RANGE'; export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export const SHOW_TIME_RANGE_INVALID_WARNING = 'SHOW_TIME_RANGE_INVALID_WARNING'; export const SHOW_TIME_RANGE_INVALID_WARNING = 'SHOW_TIME_RANGE_INVALID_WARNING';
...@@ -12,6 +13,9 @@ export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCC ...@@ -12,6 +13,9 @@ export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCC
export const RECEIVE_ENVIRONMENTS_DATA_ERROR = 'RECEIVE_ENVIRONMENTS_DATA_ERROR'; export const RECEIVE_ENVIRONMENTS_DATA_ERROR = 'RECEIVE_ENVIRONMENTS_DATA_ERROR';
export const HIDE_REQUEST_ENVIRONMENTS_ERROR = 'HIDE_REQUEST_ENVIRONMENTS_ERROR'; export const HIDE_REQUEST_ENVIRONMENTS_ERROR = 'HIDE_REQUEST_ENVIRONMENTS_ERROR';
export const RECEIVE_MANAGED_APPS_DATA_SUCCESS = 'RECEIVE_MANAGED_APPS_DATA_SUCCESS';
export const RECEIVE_MANAGED_APPS_DATA_ERROR = 'RECEIVE_MANAGED_APPS_DATA_ERROR';
export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA'; export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA';
export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS'; export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS';
export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR'; export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR';
......
...@@ -32,6 +32,9 @@ export default { ...@@ -32,6 +32,9 @@ export default {
// Clear current pod options // Clear current pod options
state.pods.current = null; state.pods.current = null;
state.pods.options = []; state.pods.options = [];
// Clear current managedApps options
state.managedApps.current = null;
}, },
[types.REQUEST_ENVIRONMENTS_DATA](state) { [types.REQUEST_ENVIRONMENTS_DATA](state) {
state.environments.options = []; state.environments.options = [];
...@@ -107,4 +110,24 @@ export default { ...@@ -107,4 +110,24 @@ export default {
[types.RECEIVE_PODS_DATA_ERROR](state) { [types.RECEIVE_PODS_DATA_ERROR](state) {
state.pods.options = []; state.pods.options = [];
}, },
// Managed apps data
[types.RECEIVE_MANAGED_APPS_DATA_SUCCESS](state, apps) {
state.managedApps.options = apps;
state.managedApps.isLoading = false;
},
[types.RECEIVE_MANAGED_APPS_DATA_ERROR](state) {
state.managedApps.options = [];
state.managedApps.isLoading = false;
state.managedApps.fetchError = true;
},
[types.SET_MANAGED_APP](state, managedApp) {
state.managedApps.current = managedApp;
// Clear current pod options
state.pods.current = null;
state.pods.options = [];
// Clear current environment options
state.environments.current = null;
},
}; };
...@@ -30,6 +30,16 @@ export default () => ({ ...@@ -30,6 +30,16 @@ export default () => ({
fetchError: false, fetchError: false,
}, },
/**
* Managed apps list information
*/
managedApps: {
options: [],
isLoading: false,
current: null,
fetchError: false,
},
/** /**
* Logs including trace * Logs including trace
*/ */
......
---
title: Add managed-apps section in log explorer
merge_request: 36769
author:
type: changed
...@@ -9112,6 +9112,9 @@ msgstr "" ...@@ -9112,6 +9112,9 @@ msgstr ""
msgid "Environments|Logs from %{start} to %{end}." msgid "Environments|Logs from %{start} to %{end}."
msgstr "" msgstr ""
msgid "Environments|Managed apps"
msgstr ""
msgid "Environments|More information" msgid "Environments|More information"
msgstr "" msgstr ""
...@@ -9166,9 +9169,6 @@ msgstr "" ...@@ -9166,9 +9169,6 @@ msgstr ""
msgid "Environments|Rollback environment %{name}?" msgid "Environments|Rollback environment %{name}?"
msgstr "" msgstr ""
msgid "Environments|Select environment"
msgstr ""
msgid "Environments|Select pod" msgid "Environments|Select pod"
msgstr "" msgstr ""
......
...@@ -12,11 +12,12 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do ...@@ -12,11 +12,12 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
let(:service) { create(:cluster_platform_kubernetes, :configured) } let(:service) { create(:cluster_platform_kubernetes, :configured) }
before do before do
create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project]) cluster = create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project])
create(:deployment, :success, environment: environment) create(:deployment, :success, environment: environment)
stub_kubeclient_pods(environment.deployment_namespace) stub_kubeclient_pods(environment.deployment_namespace)
stub_kubeclient_logs(pod_name, environment.deployment_namespace, container: 'container-0') stub_kubeclient_logs(pod_name, environment.deployment_namespace, container: 'container-0')
stub_kubeclient_nodes_and_nodes_metrics(cluster.platform.api_url)
sign_in(project.owner) sign_in(project.owner)
end end
...@@ -37,7 +38,7 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do ...@@ -37,7 +38,7 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
dropdown_items = find(".dropdown-menu").all(".dropdown-item") dropdown_items = find(".dropdown-menu").all(".dropdown-item")
expect(dropdown_items.first).to have_content(environment.name) expect(dropdown_items.first).to have_content(environment.name)
expect(dropdown_items.size).to eq(2) expect(dropdown_items.size).to eq(3)
end end
end end
......
...@@ -12,6 +12,7 @@ import { ...@@ -12,6 +12,7 @@ import {
mockTrace, mockTrace,
mockEnvironmentsEndpoint, mockEnvironmentsEndpoint,
mockDocumentationPath, mockDocumentationPath,
mockManagedAppsEndpoint,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/lib/utils/scroll_utils'); jest.mock('~/lib/utils/scroll_utils');
...@@ -34,6 +35,7 @@ describe('EnvironmentLogs', () => { ...@@ -34,6 +35,7 @@ describe('EnvironmentLogs', () => {
environmentName: mockEnvName, environmentName: mockEnvName,
environmentsPath: mockEnvironmentsEndpoint, environmentsPath: mockEnvironmentsEndpoint,
clusterApplicationsDocumentationPath: mockDocumentationPath, clusterApplicationsDocumentationPath: mockDocumentationPath,
clustersPath: mockManagedAppsEndpoint,
}; };
const updateControlBtnsMock = jest.fn(); const updateControlBtnsMock = jest.fn();
......
...@@ -7,6 +7,8 @@ export const mockDocumentationPath = '/documentation.md'; ...@@ -7,6 +7,8 @@ export const mockDocumentationPath = '/documentation.md';
export const mockLogsEndpoint = '/dummy_logs_path.json'; export const mockLogsEndpoint = '/dummy_logs_path.json';
export const mockCursor = 'MOCK_CURSOR'; export const mockCursor = 'MOCK_CURSOR';
export const mockNextCursor = 'MOCK_NEXT_CURSOR'; export const mockNextCursor = 'MOCK_NEXT_CURSOR';
export const mockManagedAppName = 'kubernetes-cluster-1';
export const mockManagedAppsEndpoint = `${mockProjectPath}/clusters.json`;
const makeMockEnvironment = (id, name, advancedQuerying) => ({ const makeMockEnvironment = (id, name, advancedQuerying) => ({
id, id,
...@@ -23,6 +25,19 @@ export const mockEnvironments = [ ...@@ -23,6 +25,19 @@ export const mockEnvironments = [
makeMockEnvironment(102, 'review/a-feature', false), makeMockEnvironment(102, 'review/a-feature', false),
]; ];
export const mockManagedApps = [
{
cluster_type: 'project_type',
enabled: true,
environment_scope: '*',
name: 'kubernetes-cluster-1',
provider_type: 'user',
status: 'connected',
path: '/root/autodevops-deploy/-/clusters/15',
gitlab_managed_apps_logs_path: '/root/autodevops-deploy/-/logs?cluster_id=15',
},
];
export const mockPodName = 'production-764c58d697-aaaaa'; export const mockPodName = 'production-764c58d697-aaaaa';
export const mockPods = [ export const mockPods = [
mockPodName, mockPodName,
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
fetchEnvironments, fetchEnvironments,
fetchLogs, fetchLogs,
fetchMoreLogsPrepend, fetchMoreLogsPrepend,
fetchManagedApps,
} from '~/logs/stores/actions'; } from '~/logs/stores/actions';
import { defaultTimeRange } from '~/vue_shared/constants'; import { defaultTimeRange } from '~/vue_shared/constants';
...@@ -30,6 +31,8 @@ import { ...@@ -30,6 +31,8 @@ import {
mockResponse, mockResponse,
mockCursor, mockCursor,
mockNextCursor, mockNextCursor,
mockManagedApps,
mockManagedAppsEndpoint,
} from '../mock_data'; } from '../mock_data';
import { TOKEN_TYPE_POD_NAME } from '~/logs/constants'; import { TOKEN_TYPE_POD_NAME } from '~/logs/constants';
...@@ -217,6 +220,30 @@ describe('Logs Store actions', () => { ...@@ -217,6 +220,30 @@ describe('Logs Store actions', () => {
}); });
}); });
describe('fetchManagedApps', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
});
it('should commit RECEIVE_MANAGED_APPS_DATA_SUCCESS mutation on succesful fetch', () => {
mock.onGet(mockManagedAppsEndpoint).replyOnce(200, { clusters: mockManagedApps });
return testAction(fetchManagedApps, mockManagedAppsEndpoint, state, [
{ type: types.RECEIVE_MANAGED_APPS_DATA_SUCCESS, payload: mockManagedApps },
]);
});
it('should commit RECEIVE_MANAGED_APPS_DATA_ERROR on wrong data', () => {
mock.onGet(mockManagedAppsEndpoint).replyOnce(500);
return testAction(
fetchManagedApps,
mockManagedAppsEndpoint,
state,
[{ type: types.RECEIVE_MANAGED_APPS_DATA_ERROR }],
[],
);
});
});
describe('when the backend responds succesfully', () => { describe('when the backend responds succesfully', () => {
let expectedMutations; let expectedMutations;
let expectedActions; let expectedActions;
......
...@@ -11,6 +11,8 @@ import { ...@@ -11,6 +11,8 @@ import {
mockSearch, mockSearch,
mockCursor, mockCursor,
mockNextCursor, mockNextCursor,
mockManagedApps,
mockManagedAppName,
} from '../mock_data'; } from '../mock_data';
describe('Logs Store Mutations', () => { describe('Logs Store Mutations', () => {
...@@ -30,6 +32,15 @@ describe('Logs Store Mutations', () => { ...@@ -30,6 +32,15 @@ describe('Logs Store Mutations', () => {
it('sets the environment', () => { it('sets the environment', () => {
mutations[types.SET_PROJECT_ENVIRONMENT](state, mockEnvName); mutations[types.SET_PROJECT_ENVIRONMENT](state, mockEnvName);
expect(state.environments.current).toEqual(mockEnvName); expect(state.environments.current).toEqual(mockEnvName);
expect(state.managedApps.current).toBe(null);
});
});
describe('SET_MANAGED_APP', () => {
it('sets the managed app', () => {
mutations[types.SET_MANAGED_APP](state, mockManagedAppName);
expect(state.managedApps.current).toBe(mockManagedAppName);
expect(state.environments.current).toBe(null);
}); });
}); });
...@@ -254,4 +265,28 @@ describe('Logs Store Mutations', () => { ...@@ -254,4 +265,28 @@ describe('Logs Store Mutations', () => {
); );
}); });
}); });
describe('RECEIVE_MANAGED_APPS_DATA_SUCCESS', () => {
it('receives managed apps data success', () => {
expect(state.managedApps.options).toEqual([]);
mutations[types.RECEIVE_MANAGED_APPS_DATA_SUCCESS](state, mockManagedApps);
expect(state.managedApps.options).toEqual(mockManagedApps);
expect(state.managedApps.isLoading).toBe(false);
});
});
describe('RECEIVE_MANAGED_APPS_DATA_ERROR', () => {
it('received managed apps data error', () => {
mutations[types.RECEIVE_MANAGED_APPS_DATA_ERROR](state);
expect(state.managedApps).toEqual({
options: [],
isLoading: false,
current: null,
fetchError: true,
});
});
});
}); });
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