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 {
GlDropdown,
GlDropdownHeader,
GlDropdownItem,
GlDropdownDivider,
GlInfiniteScroll,
} from '@gitlab/ui';
......@@ -27,6 +28,7 @@ export default {
GlDropdown,
GlDropdownHeader,
GlDropdownItem,
GlDropdownDivider,
GlInfiniteScroll,
LogSimpleFilters,
LogAdvancedFilters,
......@@ -55,6 +57,10 @@ export default {
type: String,
required: true,
},
clustersPath: {
type: String,
required: true,
},
},
data() {
return {
......@@ -63,7 +69,7 @@ export default {
};
},
computed: {
...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods']),
...mapState('environmentLogs', ['environments', 'timeRange', 'logs', 'pods', 'managedApps']),
...mapGetters('environmentLogs', ['trace', 'showAdvancedFilters']),
showLoader() {
......@@ -85,12 +91,15 @@ export default {
});
this.fetchEnvironments(this.environmentsPath);
this.fetchManagedApps(this.clustersPath);
},
methods: {
...mapActions('environmentLogs', [
'setInitData',
'showEnvironment',
'showManagedApp',
'fetchEnvironments',
'fetchManagedApps',
'refreshPodLogs',
'fetchMoreLogsPrepend',
'dismissRequestEnvironmentsError',
......@@ -101,6 +110,9 @@ export default {
isCurrentEnvironment(envName) {
return envName === this.environments.current;
},
isCurrentManagedApp(appName) {
return appName === this.managedApps.current;
},
topReached() {
if (!this.logs.isLoading) {
this.fetchMoreLogsPrepend();
......@@ -164,12 +176,12 @@ export default {
<div class="flex-grow-0">
<gl-dropdown
id="environments-dropdown"
:text="environments.current"
:text="environments.current || managedApps.current"
:disabled="environments.isLoading"
class="mb-2 gl-h-32 pr-2 d-flex d-md-block js-environments-dropdown"
>
<gl-dropdown-header class="text-center">
{{ s__('Environments|Select environment') }}
<gl-dropdown-header class="gl-text-center">
{{ s__('Environments|Environments') }}
</gl-dropdown-header>
<gl-dropdown-item
v-for="env in environments.options"
......@@ -181,7 +193,24 @@ export default {
:class="{ invisible: !isCurrentEnvironment(env.name) }"
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>
</gl-dropdown-item>
</gl-dropdown>
......
......@@ -8,4 +8,10 @@ export const tracking = {
TIME_RANGE_SET: 'time_range_set',
ENVIRONMENT_SELECTED: 'environment_selected',
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';
import httpStatusCodes from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
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 * as types from './mutation_types';
......@@ -25,9 +25,15 @@ const requestUntilData = (url, params) =>
const requestLogsUntilData = ({ commit, state }) => {
const params = {};
const { logs_api_path } = state.environments.options.find(
({ name }) => name === state.environments.current,
);
const type = 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) {
params.pod_name = state.pods.current;
......@@ -48,7 +54,7 @@ const requestLogsUntilData = ({ commit, state }) => {
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) => {
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 }) => {
commit(types.REFRESH_POD_LOGS);
dispatch('fetchLogs', tracking.REFRESH_POD_LOGS);
......@@ -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) => {
commit(types.REQUEST_LOGS_DATA);
......
export const SET_PROJECT_ENVIRONMENT = 'SET_PROJECT_ENVIRONMENT';
export const SET_SEARCH = 'SET_SEARCH';
export const SET_MANAGED_APP = 'SET_MANAGED_APP';
export const SET_TIME_RANGE = 'SET_TIME_RANGE';
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
export const RECEIVE_ENVIRONMENTS_DATA_ERROR = 'RECEIVE_ENVIRONMENTS_DATA_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 RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS';
export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR';
......
......@@ -32,6 +32,9 @@ export default {
// Clear current pod options
state.pods.current = null;
state.pods.options = [];
// Clear current managedApps options
state.managedApps.current = null;
},
[types.REQUEST_ENVIRONMENTS_DATA](state) {
state.environments.options = [];
......@@ -107,4 +110,24 @@ export default {
[types.RECEIVE_PODS_DATA_ERROR](state) {
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 () => ({
fetchError: false,
},
/**
* Managed apps list information
*/
managedApps: {
options: [],
isLoading: false,
current: null,
fetchError: false,
},
/**
* Logs including trace
*/
......
---
title: Add managed-apps section in log explorer
merge_request: 36769
author:
type: changed
......@@ -9112,6 +9112,9 @@ msgstr ""
msgid "Environments|Logs from %{start} to %{end}."
msgstr ""
msgid "Environments|Managed apps"
msgstr ""
msgid "Environments|More information"
msgstr ""
......@@ -9166,9 +9169,6 @@ msgstr ""
msgid "Environments|Rollback environment %{name}?"
msgstr ""
msgid "Environments|Select environment"
msgstr ""
msgid "Environments|Select pod"
msgstr ""
......
......@@ -12,11 +12,12 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
let(:service) { create(:cluster_platform_kubernetes, :configured) }
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)
stub_kubeclient_pods(environment.deployment_namespace)
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)
end
......@@ -37,7 +38,7 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
dropdown_items = find(".dropdown-menu").all(".dropdown-item")
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
......
......@@ -12,6 +12,7 @@ import {
mockTrace,
mockEnvironmentsEndpoint,
mockDocumentationPath,
mockManagedAppsEndpoint,
} from '../mock_data';
jest.mock('~/lib/utils/scroll_utils');
......@@ -34,6 +35,7 @@ describe('EnvironmentLogs', () => {
environmentName: mockEnvName,
environmentsPath: mockEnvironmentsEndpoint,
clusterApplicationsDocumentationPath: mockDocumentationPath,
clustersPath: mockManagedAppsEndpoint,
};
const updateControlBtnsMock = jest.fn();
......
......@@ -7,6 +7,8 @@ export const mockDocumentationPath = '/documentation.md';
export const mockLogsEndpoint = '/dummy_logs_path.json';
export const mockCursor = 'MOCK_CURSOR';
export const mockNextCursor = 'MOCK_NEXT_CURSOR';
export const mockManagedAppName = 'kubernetes-cluster-1';
export const mockManagedAppsEndpoint = `${mockProjectPath}/clusters.json`;
const makeMockEnvironment = (id, name, advancedQuerying) => ({
id,
......@@ -23,6 +25,19 @@ export const mockEnvironments = [
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 mockPods = [
mockPodName,
......
......@@ -11,6 +11,7 @@ import {
fetchEnvironments,
fetchLogs,
fetchMoreLogsPrepend,
fetchManagedApps,
} from '~/logs/stores/actions';
import { defaultTimeRange } from '~/vue_shared/constants';
......@@ -30,6 +31,8 @@ import {
mockResponse,
mockCursor,
mockNextCursor,
mockManagedApps,
mockManagedAppsEndpoint,
} from '../mock_data';
import { TOKEN_TYPE_POD_NAME } from '~/logs/constants';
......@@ -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', () => {
let expectedMutations;
let expectedActions;
......
......@@ -11,6 +11,8 @@ import {
mockSearch,
mockCursor,
mockNextCursor,
mockManagedApps,
mockManagedAppName,
} from '../mock_data';
describe('Logs Store Mutations', () => {
......@@ -30,6 +32,15 @@ describe('Logs Store Mutations', () => {
it('sets the environment', () => {
mutations[types.SET_PROJECT_ENVIRONMENT](state, 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', () => {
);
});
});
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