Commit 34befe4c authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent b9807875a772
......@@ -348,10 +348,11 @@ RSpec/HaveGitlabHttpStatus:
Include:
- 'spec/support/shared_examples/**/*'
- 'ee/spec/support/shared_examples/**/*'
- 'spec/features/**/*'
- 'ee/spec/features/**/*'
Style/MultilineWhenThen:
Enabled: false
Style/FloatDivision:
Enabled: false
\ No newline at end of file
/**
* Ids generated by GraphQL endpoints are usually in the format
* gid://gitlab/Environments/123. This method extracts Id number
* from the Id path
*
* @param {String} gid GraphQL global ID
* @returns {Number}
*/
export const getIdFromGraphQLId = (gid = '') =>
parseInt((gid || '').replace(/gid:\/\/gitlab\/.*\//g, ''), 10) || null;
export default {};
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlBadge } from '@gitlab/ui';
import { visitUrl } from '../../lib/utils/url_utility';
import tooltip from '../../vue_shared/directives/tooltip';
import identicon from '../../vue_shared/components/identicon.vue';
......@@ -17,6 +17,7 @@ export default {
tooltip,
},
components: {
GlBadge,
GlLoadingIcon,
identicon,
itemCaret,
......@@ -62,6 +63,9 @@ export default {
isGroup() {
return this.group.type === 'group';
},
isGroupPendingRemoval() {
return this.group.type === 'group' && this.group.pendingRemoval;
},
visibilityIcon() {
return VISIBILITY_TYPE_ICON[this.group.visibility];
},
......@@ -139,6 +143,9 @@ export default {
<span v-html="group.description"> </span>
</div>
</div>
<div v-if="isGroupPendingRemoval">
<gl-badge variant="warning">{{ __('pending removal') }}</gl-badge>
</div>
<div
class="metadata align-items-md-center d-flex flex-grow-1 flex-shrink-0 flex-wrap justify-content-md-between"
>
......
......@@ -93,7 +93,7 @@ export default class GroupsStore {
memberCount: rawGroupItem.number_users_with_delimiter,
starCount: rawGroupItem.star_count,
updatedAt: rawGroupItem.updated_at,
pendingRemoval: rawGroupItem.marked_for_deletion_at,
pendingRemoval: rawGroupItem.marked_for_deletion,
};
}
......
query getEnvironments($projectPath: ID!, $search: String) {
project(fullPath: $projectPath) {
data: environments(search: $search) {
environments: nodes {
name
id
}
}
}
}
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { gqClient, parseEnvironmentsResponse, removeLeadingSlash } from './utils';
import trackDashboardLoad from '../monitoring_tracking_helper';
import getEnvironments from '../queries/getEnvironments.query.graphql';
import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
import { s__, sprintf } from '../../locale';
......@@ -187,26 +189,30 @@ export const fetchDeploymentsData = ({ state, dispatch }) => {
});
};
export const fetchEnvironmentsData = ({ state, dispatch }) => {
if (!state.environmentsEndpoint) {
return Promise.resolve([]);
}
return axios
.get(state.environmentsEndpoint)
.then(resp => resp.data)
.then(response => {
if (!response || !response.environments) {
export const fetchEnvironmentsData = ({ state, dispatch }) =>
gqClient
.mutate({
mutation: getEnvironments,
variables: {
projectPath: removeLeadingSlash(state.projectPath),
search: state.environmentsSearchTerm,
},
})
.then(resp =>
parseEnvironmentsResponse(resp.data?.project?.data?.environments, state.projectPath),
)
.then(environments => {
if (!environments) {
createFlash(
s__('Metrics|There was an error fetching the environments data, please try again'),
);
}
dispatch('receiveEnvironmentsDataSuccess', response.environments);
dispatch('receiveEnvironmentsDataSuccess', environments);
})
.catch(() => {
dispatch('receiveEnvironmentsDataFailure');
createFlash(s__('Metrics|There was an error getting environments information.'));
});
};
/**
* Set a new array of metrics to a panel group
......
import { omit } from 'lodash';
import createGqClient from '~/lib/graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
export const gqClient = createGqClient();
export const uniqMetricsId = metric => `${metric.metric_id}_${metric.id}`;
/**
* Project path has a leading slash that doesn't work well
* with project full path resolver here
* https://gitlab.com/gitlab-org/gitlab/blob/5cad4bd721ab91305af4505b2abc92b36a56ad6b/app/graphql/resolvers/full_path_resolver.rb#L10
*
* @param {String} str String with leading slash
* @returns {String}
*/
export const removeLeadingSlash = str => (str || '').replace(/^\/+/, '');
/**
* GraphQL environments API returns only id and name.
* For the environments dropdown we need metrics_path.
* This method parses the results and add neccessart attrs
*
* @param {Array} response Environments API result
* @param {String} projectPath Current project path
* @returns {Array}
*/
export const parseEnvironmentsResponse = (response = [], projectPath) =>
(response || []).map(env => {
const id = getIdFromGraphQLId(env.id);
return {
...env,
id,
metrics_path: `${projectPath}/environments/${id}/metrics`,
};
});
/**
* Metrics loaded from project-defined dashboards do not have a metric_id.
* This method creates a unique ID combining metric_id and id, if either is present.
......
<script>
import { mapActions } from 'vuex';
import { mapActions, mapState } from 'vuex';
import { GlAlert } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import { FETCH_SETTINGS_ERROR_MESSAGE } from '../constants';
import SettingsForm from './settings_form.vue';
......@@ -7,6 +10,23 @@ import SettingsForm from './settings_form.vue';
export default {
components: {
SettingsForm,
GlAlert,
},
computed: {
...mapState(['isDisabled']),
notAvailableMessage() {
return sprintf(
s__(
'ContainerRegistry|Currently, the Container Registry tag expiration feature is not available for projects created before GitLab version 12.8. For updates and more information, visit Issue %{linkStart}#196124%{linkEnd}',
),
{
linkStart:
'<a href="https://gitlab.com/gitlab-org/gitlab/issues/196124" target="_blank" rel="noopener noreferrer">',
linkEnd: '</a>',
},
false,
);
},
},
mounted() {
this.fetchSettings().catch(() =>
......@@ -34,6 +54,9 @@ export default {
}}
</li>
</ul>
<settings-form ref="settings-form" />
<settings-form v-if="!isDisabled" />
<gl-alert v-else :dismissible="false">
<p v-html="notAvailableMessage"></p>
</gl-alert>
</div>
</template>
......@@ -4,7 +4,13 @@ import * as types from './mutation_types';
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
export const updateSettings = ({ commit }, data) => commit(types.UPDATE_SETTINGS, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING);
export const receiveSettingsSuccess = ({ commit }, data = {}) => commit(types.SET_SETTINGS, data);
export const receiveSettingsSuccess = ({ commit }, data) => {
if (data) {
commit(types.SET_SETTINGS, data);
} else {
commit(types.SET_IS_DISABLED, true);
}
};
export const resetSettings = ({ commit }) => commit(types.RESET_SETTINGS);
export const fetchSettings = ({ dispatch, state }) => {
......
......@@ -3,3 +3,4 @@ export const UPDATE_SETTINGS = 'UPDATE_SETTINGS';
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
export const SET_SETTINGS = 'SET_SETTINGS';
export const RESET_SETTINGS = 'RESET_SETTINGS';
export const SET_IS_DISABLED = 'SET_IS_DISABLED';
......@@ -16,6 +16,9 @@ export default {
state.settings = settings;
state.original = Object.freeze(settings);
},
[types.SET_IS_DISABLED](state, isDisabled) {
state.isDisabled = isDisabled;
},
[types.RESET_SETTINGS](state) {
state.settings = { ...state.original };
},
......
......@@ -7,6 +7,10 @@ export default () => ({
* Boolean to determine if the UI is loading data from the API
*/
isLoading: false,
/*
* Boolean to determine if the user is allowed to interact with the form
*/
isDisabled: false,
/*
* This contains the data shown and manipulated in the UI
* Has the following structure:
......
......@@ -56,6 +56,7 @@
.gl-bg-green-100 { @include gl-bg-green-100;}
.gl-text-blue-500 { @include gl-text-blue-500; }
.gl-text-gray-700 { @include gl-text-gray-700; }
.gl-text-gray-900 { @include gl-text-gray-900; }
.gl-text-red-700 { @include gl-text-red-700; }
.gl-text-orange-700 { @include gl-text-orange-700; }
......
......@@ -6,8 +6,7 @@ class Admin::GroupsController < Admin::ApplicationController
before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
def index
@groups = Group.with_statistics.with_route
@groups = @groups.sort_by_attribute(@sort = params[:sort])
@groups = groups.sort_by_attribute(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page])
end
......@@ -75,6 +74,10 @@ def destroy
private
def groups
Group.with_statistics.with_route
end
def group
@group ||= Group.find_by_full_path(params[:id])
end
......
......@@ -709,4 +709,10 @@ def vue_file_list_enabled?
def show_visibility_confirm_modal?(project)
project.unlink_forks_upon_visibility_decrease_enabled? && project.visibility_level > Gitlab::VisibilityLevel::PRIVATE && project.forks_count > 0
end
def settings_container_registry_expiration_policy_available?(project)
Feature.enabled?(:registry_retention_policies_settings, project) &&
Gitlab.config.registry.enabled &&
can?(current_user, :read_container_image, project)
end
end
......@@ -73,3 +73,5 @@ def member_count
@member_count ||= try(:preloaded_member_count) || users.count
end
end
LoadedInGroupList::ClassMethods.prepend_if_ee('EE::LoadedInGroupList::ClassMethods')
......@@ -467,6 +467,10 @@ def export_file
import_export_upload&.export_file
end
def adjourned_deletion?
false
end
private
def update_two_factor_requirement
......
......@@ -10,6 +10,7 @@
= storage_counter(group.storage_size)
= render_if_exists 'admin/namespace_plan_badge', namespace: group
= render_if_exists 'admin/groups/marked_for_deletion_badge', group: group
%span
= icon('bookmark')
......
......@@ -39,12 +39,5 @@
%li= s_("GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.")
= f.submit s_('GroupSettings|Transfer group'), class: 'btn btn-warning'
.sub-section
%h4.danger-title= _('Remove group')
= form_tag(@group, method: :delete) do
%p
= _('Removing group will cause all child projects and resources to be removed.')
%br
%strong= _('Removed group can not be restored!')
= button_to _('Remove group'), '#', class: 'btn btn-remove js-confirm-danger', data: { 'confirm-danger-message' => remove_group_message(@group) }
= render 'groups/settings/remove', group: @group
= render_if_exists 'groups/settings/restore', group: @group
.sub-section
%h4.danger-title= _('Remove group')
= form_tag(group, method: :delete) do
%p
= _('Removing group will cause all child projects and resources to be removed.')
%br
%strong= _('Removed group can not be restored!')
= button_to _('Remove group'), '#', class: 'btn btn-remove js-confirm-danger', data: { 'confirm-danger-message' => remove_group_message(group) }
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