diff options
Diffstat (limited to 'app')
55 files changed, 301 insertions, 201 deletions
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index d309fea3c98..5c9a28b8512 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -160,10 +160,14 @@ export default { return expanded || this.alwaysExpanded || isResolvedNonDiffDiscussion; }, actionText() { - const commitId = this.discussion.commit_id ? truncateSha(this.discussion.commit_id) : ''; const linkStart = `<a href="${_.escape(this.discussion.discussion_path)}">`; const linkEnd = '</a>'; + let { commit_id: commitId } = this.discussion; + if (commitId) { + commitId = `<span class="commit-sha">${truncateSha(commitId)}</span>`; + } + let text = s__('MergeRequests|started a discussion'); if (this.discussion.for_commit) { diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index a3228f2cfea..39ff0ff73d7 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -246,7 +246,7 @@ export default { discussion => !discussion.individual_note && discussion.resolvable && - discussion.notes.some(note => !note.resolved), + discussion.notes.some(note => note.resolvable && !note.resolved), ).length; state.hasUnresolvedDiscussions = state.unresolvedDiscussionsCount > 1; }, diff --git a/app/assets/javascripts/pages/profiles/show/emoji_menu.js b/app/assets/javascripts/pages/profiles/show/emoji_menu.js index 094837b40e0..286c1f1e929 100644 --- a/app/assets/javascripts/pages/profiles/show/emoji_menu.js +++ b/app/assets/javascripts/pages/profiles/show/emoji_menu.js @@ -1,3 +1,4 @@ +import '~/commons/bootstrap'; import { AwardsHandler } from '~/awards_handler'; class EmojiMenu extends AwardsHandler { diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue index 6233fb169e9..9af5660f764 100644 --- a/app/assets/javascripts/registry/components/app.vue +++ b/app/assets/javascripts/registry/components/app.vue @@ -1,15 +1,13 @@ <script> import { mapGetters, mapActions } from 'vuex'; import { GlLoadingIcon } from '@gitlab/ui'; -import Flash from '../../flash'; import store from '../stores'; -import collapsibleContainer from './collapsible_container.vue'; -import { errorMessages, errorMessagesTypes } from '../constants'; +import CollapsibleContainer from './collapsible_container.vue'; export default { name: 'RegistryListApp', components: { - collapsibleContainer, + CollapsibleContainer, GlLoadingIcon, }, props: { @@ -26,7 +24,7 @@ export default { this.setMainEndpoint(this.endpoint); }, mounted() { - this.fetchRepos().catch(() => Flash(errorMessages[errorMessagesTypes.FETCH_REPOS])); + this.fetchRepos(); }, methods: { ...mapActions(['setMainEndpoint', 'fetchRepos']), @@ -38,9 +36,9 @@ export default { <gl-loading-icon v-if="isLoading" :size="3" /> <collapsible-container - v-for="(item, index) in repos" + v-for="item in repos" v-else-if="!isLoading && repos.length" - :key="index" + :key="item.id" :repo="item" /> diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index 6514c05a9c7..5451c61026c 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -1,22 +1,24 @@ <script> import { mapActions } from 'vuex'; -import { GlLoadingIcon } from '@gitlab/ui'; -import Flash from '../../flash'; -import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; -import tooltip from '../../vue_shared/directives/tooltip'; -import tableRegistry from './table_registry.vue'; +import { GlLoadingIcon, GlButton, GlTooltipDirective } from '@gitlab/ui'; +import createFlash from '../../flash'; +import ClipboardButton from '../../vue_shared/components/clipboard_button.vue'; +import Icon from '../../vue_shared/components/icon.vue'; +import TableRegistry from './table_registry.vue'; import { errorMessages, errorMessagesTypes } from '../constants'; import { __ } from '../../locale'; export default { name: 'CollapsibeContainerRegisty', components: { - clipboardButton, - tableRegistry, + ClipboardButton, + TableRegistry, GlLoadingIcon, + GlButton, + Icon, }, directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, props: { repo: { @@ -29,30 +31,30 @@ export default { isOpen: false, }; }, + computed: { + iconName() { + return this.isOpen ? 'angle-up' : 'angle-right'; + }, + }, methods: { ...mapActions(['fetchRepos', 'fetchList', 'deleteRepo']), - toggleRepo() { this.isOpen = !this.isOpen; if (this.isOpen) { - this.fetchList({ repo: this.repo }).catch(() => - this.showError(errorMessagesTypes.FETCH_REGISTRY), - ); + this.fetchList({ repo: this.repo }); } }, - handleDeleteRepository() { this.deleteRepo(this.repo) .then(() => { - Flash(__('This container registry has been scheduled for deletion.'), 'notice'); + createFlash(__('This container registry has been scheduled for deletion.'), 'notice'); this.fetchRepos(); }) .catch(() => this.showError(errorMessagesTypes.DELETE_REPO)); }, - showError(message) { - Flash(errorMessages[message]); + createFlash(errorMessages[message]); }, }, }; @@ -61,18 +63,9 @@ export default { <template> <div class="container-image"> <div class="container-image-head"> - <button type="button" class="js-toggle-repo btn-link" @click="toggleRepo"> - <i - :class="{ - 'fa-chevron-right': !isOpen, - 'fa-chevron-up': isOpen, - }" - class="fa" - aria-hidden="true" - > - </i> - {{ repo.name }} - </button> + <gl-button class="js-toggle-repo btn-link align-baseline" @click="toggleRepo"> + <icon :name="iconName" /> {{ repo.name }} + </gl-button> <clipboard-button v-if="repo.location" @@ -82,17 +75,17 @@ export default { /> <div class="controls d-none d-sm-block float-right"> - <button + <gl-button v-if="repo.canDelete" - v-tooltip + v-gl-tooltip :title="s__('ContainerRegistry|Remove repository')" :aria-label="s__('ContainerRegistry|Remove repository')" - type="button" - class="js-remove-repo btn btn-danger" + class="js-remove-repo" + variant="danger" @click="handleDeleteRepository" > - <i class="fa fa-trash" aria-hidden="true"> </i> - </button> + <icon name="remove" /> + </gl-button> </div> </div> diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue index 6735c3ff7cf..78c7671856a 100644 --- a/app/assets/javascripts/registry/components/table_registry.vue +++ b/app/assets/javascripts/registry/components/table_registry.vue @@ -1,21 +1,24 @@ <script> import { mapActions } from 'vuex'; +import { GlButton, GlTooltipDirective } from '@gitlab/ui'; import { n__ } from '../../locale'; -import Flash from '../../flash'; -import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; -import tablePagination from '../../vue_shared/components/table_pagination.vue'; -import tooltip from '../../vue_shared/directives/tooltip'; +import createFlash from '../../flash'; +import ClipboardButton from '../../vue_shared/components/clipboard_button.vue'; +import TablePagination from '../../vue_shared/components/table_pagination.vue'; +import Icon from '../../vue_shared/components/icon.vue'; import timeagoMixin from '../../vue_shared/mixins/timeago'; import { errorMessages, errorMessagesTypes } from '../constants'; import { numberToHumanSize } from '../../lib/utils/number_utils'; export default { components: { - clipboardButton, - tablePagination, + ClipboardButton, + TablePagination, + GlButton, + Icon, }, directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, mixins: [timeagoMixin], props: { @@ -31,29 +34,24 @@ export default { }, methods: { ...mapActions(['fetchList', 'deleteRegistry']), - layers(item) { return item.layers ? n__('%d layer', '%d layers', item.layers) : ''; }, - formatSize(size) { return numberToHumanSize(size); }, - handleDeleteRegistry(registry) { this.deleteRegistry(registry) .then(() => this.fetchList({ repo: this.repo })) .catch(() => this.showError(errorMessagesTypes.DELETE_REGISTRY)); }, - onPageChange(pageNumber) { this.fetchList({ repo: this.repo, page: pageNumber }).catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY), ); }, - showError(message) { - Flash(errorMessages[message]); + createFlash(errorMessages[message]); }, }, }; @@ -71,10 +69,9 @@ export default { </tr> </thead> <tbody> - <tr v-for="(item, i) in repo.list" :key="i"> + <tr v-for="item in repo.list" :key="item.tag"> <td> {{ item.tag }} - <clipboard-button v-if="item.location" :title="item.location" @@ -83,37 +80,34 @@ export default { /> </td> <td> - <span v-tooltip :title="item.revision" data-placement="bottom"> - {{ item.shortRevision }} - </span> + <span v-gl-tooltip.bottom :title="item.revision">{{ item.shortRevision }}</span> </td> <td> {{ formatSize(item.size) }} - <template v-if="item.size && item.layers"> - · - </template> + <template v-if="item.size && item.layers" + >·</template + > {{ layers(item) }} </td> <td> - <span v-tooltip :title="tooltipTitle(item.createdAt)" data-placement="bottom"> - {{ timeFormated(item.createdAt) }} - </span> + <span v-gl-tooltip.bottom :title="tooltipTitle(item.createdAt)">{{ + timeFormated(item.createdAt) + }}</span> </td> <td class="content"> - <button + <gl-button v-if="item.canDelete" - v-tooltip + v-gl-tooltip :title="s__('ContainerRegistry|Remove tag')" :aria-label="s__('ContainerRegistry|Remove tag')" - type="button" - class="js-delete-registry btn btn-danger d-none d-sm-block float-right" - data-container="body" + variant="danger" + class="js-delete-registry d-none d-sm-block float-right" @click="handleDeleteRegistry(item);" > - <i class="fa fa-trash" aria-hidden="true"> </i> - </button> + <icon name="remove" /> + </gl-button> </td> </tr> </tbody> diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js index a78aa90b7b5..51d057c62c1 100644 --- a/app/assets/javascripts/registry/stores/actions.js +++ b/app/assets/javascripts/registry/stores/actions.js @@ -1,39 +1,45 @@ -import Vue from 'vue'; -import VueResource from 'vue-resource'; +import axios from '~/lib/utils/axios_utils'; +import createFlash from '~/flash'; import * as types from './mutation_types'; - -Vue.use(VueResource); +import { errorMessages, errorMessagesTypes } from '../constants'; export const fetchRepos = ({ commit, state }) => { commit(types.TOGGLE_MAIN_LOADING); - return Vue.http + return axios .get(state.endpoint) - .then(res => res.json()) - .then(response => { + .then(({ data }) => { + commit(types.TOGGLE_MAIN_LOADING); + commit(types.SET_REPOS_LIST, data); + }) + .catch(() => { commit(types.TOGGLE_MAIN_LOADING); - commit(types.SET_REPOS_LIST, response); + createFlash(errorMessages[errorMessagesTypes.FETCH_REPOS]); }); }; export const fetchList = ({ commit }, { repo, page }) => { commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); - return Vue.http.get(repo.tagsPath, { params: { page } }).then(response => { - const { headers } = response; + return axios + .get(repo.tagsPath, { params: { page } }) + .then(response => { + const { headers, data } = response; - return response.json().then(resp => { commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); - commit(types.SET_REGISTRY_LIST, { repo, resp, headers }); + commit(types.SET_REGISTRY_LIST, { repo, resp: data, headers }); + }) + .catch(() => { + commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); + createFlash(errorMessages[errorMessagesTypes.FETCH_REGISTRY]); }); - }); }; // eslint-disable-next-line no-unused-vars -export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath); +export const deleteRepo = ({ commit }, repo) => axios.delete(repo.destroyPath); // eslint-disable-next-line no-unused-vars -export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath); +export const deleteRegistry = ({ commit }, image) => axios.delete(image.destroyPath); export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING); diff --git a/app/assets/javascripts/registry/stores/index.js b/app/assets/javascripts/registry/stores/index.js index 78b67881210..1bb06bd6e81 100644 --- a/app/assets/javascripts/registry/stores/index.js +++ b/app/assets/javascripts/registry/stores/index.js @@ -3,36 +3,12 @@ import Vuex from 'vuex'; import * as actions from './actions'; import * as getters from './getters'; import mutations from './mutations'; +import createState from './state'; Vue.use(Vuex); export default new Vuex.Store({ - state: { - isLoading: false, - endpoint: '', // initial endpoint to fetch the repos list - /** - * Each object in `repos` has the following strucure: - * { - * name: String, - * isLoading: Boolean, - * tagsPath: String // endpoint to request the list - * destroyPath: String // endpoit to delete the repo - * list: Array // List of the registry images - * } - * - * Each registry image inside `list` has the following structure: - * { - * tag: String, - * revision: String - * shortRevision: String - * size: Number - * layers: Number - * createdAt: String - * destroyPath: String // endpoit to delete each image - * } - */ - repos: [], - }, + state: createState(), actions, getters, mutations, diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js index 69c051cd2d6..1ac699c538f 100644 --- a/app/assets/javascripts/registry/stores/mutations.js +++ b/app/assets/javascripts/registry/stores/mutations.js @@ -48,6 +48,7 @@ export default { [types.TOGGLE_REGISTRY_LIST_LOADING](state, list) { const listToUpdate = state.repos.find(el => el.id === list.id); + listToUpdate.isLoading = !listToUpdate.isLoading; }, }; diff --git a/app/assets/javascripts/registry/stores/state.js b/app/assets/javascripts/registry/stores/state.js new file mode 100644 index 00000000000..feeac10cbe1 --- /dev/null +++ b/app/assets/javascripts/registry/stores/state.js @@ -0,0 +1,26 @@ +export default () => ({ + isLoading: false, + endpoint: '', // initial endpoint to fetch the repos list + /** + * Each object in `repos` has the following strucure: + * { + * name: String, + * isLoading: Boolean, + * tagsPath: String // endpoint to request the list + * destroyPath: String // endpoit to delete the repo + * list: Array // List of the registry images + * } + * + * Each registry image inside `list` has the following structure: + * { + * tag: String, + * revision: String + * shortRevision: String + * size: Number + * layers: Number + * createdAt: String + * destroyPath: String // endpoit to delete each image + * } + */ + repos: [], +}); diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index f3c44f32d6f..f273eb9533d 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -176,9 +176,9 @@ display: block; font-weight: $gl-font-weight-normal; position: relative; - padding: 8px 16px; + padding: $dropdown-item-padding-y $dropdown-item-padding-x; color: $gl-text-color; - line-height: normal; + line-height: $gl-btn-line-height; white-space: normal; overflow: hidden; text-align: left; @@ -319,8 +319,8 @@ .dropdown-header { color: $gl-text-color-secondary; font-size: 13px; - line-height: 22px; - padding: 8px 16px; + line-height: $gl-line-height; + padding: $dropdown-item-padding-y $dropdown-item-padding-x; } &.capitalize-header .dropdown-header { @@ -329,13 +329,8 @@ .dropdown-bold-header { font-weight: $gl-font-weight-bold; - line-height: 22px; - padding: 0 16px; - } - - .separator + .dropdown-header, - .separator + .dropdown-bold-header { - padding-top: 10px; + line-height: $gl-line-height; + padding: $dropdown-item-padding-y $dropdown-item-padding-x; } .unclickable { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index c0cda29e239..45a52d99302 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -90,12 +90,6 @@ padding: 2px 8px; margin: 5px 2px 5px -8px; border-radius: $border-radius-default; - - .tanuki-logo { - @include media-breakpoint-up(sm) { - margin-right: 8px; - } - } } .project-item-select { @@ -127,12 +121,6 @@ } } - li.dropdown-bold-header { - color: $gl-text-color-secondary; - font-size: 12px; - padding: 0 16px; - } - .navbar-collapse { flex: 0 0 auto; border-top: 0; @@ -541,7 +529,7 @@ left: auto; li.current-user { - padding: 5px 18px; + padding: $dropdown-item-padding-y $dropdown-item-padding-x; .user-name { display: block; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 2dba2c61631..5310195d9c5 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -400,7 +400,7 @@ $award-emoji-positive-add-lines: #bb9c13; * Search Box */ $search-input-border-color: rgba($blue-400, 0.8); -$search-input-width: 240px; +$search-input-width: 200px; $search-input-active-width: 320px; $location-icon-color: #e7e9ed; diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss index b12305f635d..5ca76bb6c5a 100644 --- a/app/assets/stylesheets/framework/variables_overrides.scss +++ b/app/assets/stylesheets/framework/variables_overrides.scss @@ -21,6 +21,8 @@ $danger: $red-500; $zindex-modal-backdrop: 1040; $nav-divider-margin-y: ($grid-size / 2); $dropdown-divider-bg: $theme-gray-200; +$dropdown-item-padding-y: 8px; +$dropdown-item-padding-x: 12px; $popover-max-width: 300px; $popover-border-width: 1px; $popover-border-color: $border-color; diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 132f3fea92b..a4831b64344 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -98,7 +98,6 @@ // Limits the width of the user bio for readability. max-width: 600px; margin: 10px auto; - padding: 0 16px; } .user-avatar-button { @@ -222,7 +221,11 @@ } .profile-header { - margin: 0 auto; + margin: 0 $gl-padding; + + &.with-no-profile-tabs { + margin-bottom: $gl-padding-24; + } .avatar-holder { width: 90px; diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 04151b1cd59..149c3254d84 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -101,8 +101,6 @@ input[type='checkbox']:hover { .dropdown-header { // Necessary because glDropdown doesn't support a second style of headers font-weight: $gl-font-weight-bold; - // .dropdown-menu li has 1px side padding - padding: $gl-padding-8 17px; color: $gl-text-color; font-size: $gl-font-size; line-height: 16px; diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 2e9c77ae55c..9aa8b758539 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -181,15 +181,15 @@ class Clusters::ClustersController < Clusters::BaseController end def gcp_cluster - @gcp_cluster = ::Clusters::Cluster.new.tap do |cluster| - cluster.build_provider_gcp - end.present(current_user: current_user) + cluster = Clusters::BuildService.new(clusterable.subject).execute + cluster.build_provider_gcp + @gcp_cluster = cluster.present(current_user: current_user) end def user_cluster - @user_cluster = ::Clusters::Cluster.new.tap do |cluster| - cluster.build_platform_kubernetes - end.present(current_user: current_user) + cluster = Clusters::BuildService.new(clusterable.subject).execute + cluster.build_platform_kubernetes + @user_cluster = cluster.present(current_user: current_user) end def validate_gcp_token diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index 57e612d89d3..f073b6de444 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -56,7 +56,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController projects = ProjectsFinder .new(params: finder_params, current_user: current_user) .execute - .includes(:route, :creator, namespace: [:route, :owner]) + .includes(:route, :creator, :group, namespace: [:route, :owner]) .page(finder_params[:page]) prepare_projects_for_rendering(projects) diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 7ecbc32cf4e..778fdda8dbd 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -57,7 +57,7 @@ class Explore::ProjectsController < Explore::ApplicationController def load_projects projects = ProjectsFinder.new(current_user: current_user, params: params) .execute - .includes(:route, namespace: :route) + .includes(:route, :creator, :group, namespace: [:route, :owner]) .page(params[:page]) .without_count diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 58565aaf8c9..d4c26fa0709 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -7,7 +7,7 @@ class Import::GithubController < Import::BaseController rescue_from Octokit::Unauthorized, with: :provider_unauthorized def new - if logged_in_with_provider? + if github_import_configured? && logged_in_with_provider? go_to_provider_for_permissions elsif session[access_token_key] redirect_to status_import_url diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 4b6c5b215e8..8d8c62f1291 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -11,6 +11,10 @@ module DropdownsHelper dropdown_output = dropdown_toggle(toggle_text, data_attr, options) + if options.key?(:toggle_link) + dropdown_output = dropdown_toggle_link(toggle_text, data_attr, options) + end + dropdown_output << content_tag(:div, class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}") do output = [] @@ -49,6 +53,11 @@ module DropdownsHelper end end + def dropdown_toggle_link(toggle_text, data_attr, options = {}) + output = content_tag(:a, toggle_text, class: "dropdown-toggle-text #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), data: data_attr) + output.html_safe + end + def dropdown_title(title, options: {}) content_tag :div, class: "dropdown-title" do title_output = [] diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 3ce2398f1de..1371e9993b4 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -161,6 +161,10 @@ module EventsHelper project_commit_url(event.project, event.note_target, anchor: dom_id(event.target)) elsif event.project_snippet_note? project_snippet_url(event.project, event.note_target, anchor: dom_id(event.target)) + elsif event.issue_note? + project_issue_url(event.project, id: event.note_target, anchor: dom_id(event.target)) + elsif event.merge_request_note? + project_merge_request_url(event.project, id: event.note_target, anchor: dom_id(event.target)) else polymorphic_url([event.project.namespace.becomes(Namespace), event.project, event.note_target], diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index a7fe8c3d59c..05da5ebdb22 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -47,8 +47,8 @@ module NavHelper class_names end - def show_separator? - Gitlab::Sherlock.enabled? || can?(current_user, :read_instance_statistics) + def has_extra_nav_icons? + Gitlab::Sherlock.enabled? || can?(current_user, :read_instance_statistics) || current_user.admin? end def page_has_markdown? diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 87aebe415c8..7c8557a1a8a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -2,7 +2,7 @@ module ProjectsHelper def link_to_project(project) - link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do + link_to namespace_project_path(namespace_id: project.namespace, id: project), title: h(project.name) do title = content_tag(:span, project.name, class: 'project-name') if project.namespace diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index cf60696ef39..2f802e4eab8 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -29,6 +29,11 @@ module SelectsHelper classes = Array.wrap(opts[:class]) classes << 'ajax-groups-select' + # EE requires this line to be present, but there is no easy way of injecting + # this into EE without causing merge conflicts. Given this line is very + # simple and not really EE specific on its own, we just include it in CE. + classes << 'multiselect' if opts[:multiple] + opts[:class] = classes.join(' ') select2_tag(id, opts) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 662f3e00047..88ad4c3e893 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -166,7 +166,7 @@ class Notify < BaseMailer headers['In-Reply-To'] = message_id(model) headers['References'] = [message_id(model)] - headers[:subject]&.prepend('Re: ') + headers[:subject] = "Re: #{headers[:subject]}" if headers[:subject] mail_thread(model, headers) end diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index baf8adb318b..277f7c2717c 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -16,14 +16,20 @@ class BroadcastMessage < ActiveRecord::Base default_value_for :color, '#E75E40' default_value_for :font, '#FFFFFF' - CACHE_KEY = 'broadcast_message_current'.freeze + CACHE_KEY = 'broadcast_message_current_json'.freeze + LEGACY_CACHE_KEY = 'broadcast_message_current'.freeze after_commit :flush_redis_cache def self.current - messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) { current_and_future_messages.to_a } + raw_messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) do + remove_legacy_cache_key + current_and_future_messages.to_json + end - return messages if messages.empty? + messages = decode_messages(raw_messages) + + return [] unless messages&.present? now_or_future = messages.select(&:now_or_future?) @@ -34,6 +40,27 @@ class BroadcastMessage < ActiveRecord::Base now_or_future.select(&:now?) end + def self.decode_messages(raw_messages) + return unless raw_messages&.present? + + message_list = ActiveSupport::JSON.decode(raw_messages) + + return unless message_list.is_a?(Array) + + valid_attr = BroadcastMessage.attribute_names + + message_list.map do |raw| + BroadcastMessage.new(raw) if valid_cache_entry?(raw, valid_attr) + end.compact + rescue ActiveSupport::JSON.parse_error + end + + def self.valid_cache_entry?(raw, valid_attr) + return false unless raw.is_a?(Hash) + + (raw.keys - valid_attr).empty? + end + def self.current_and_future_messages where('ends_at > :now', now: Time.zone.now).order_id_asc end @@ -42,6 +69,14 @@ class BroadcastMessage < ActiveRecord::Base nil end + # This can be removed in GitLab 12.0+ + # The old cache key had an indefinite lifetime, and in an HA + # environment a one-shot migration would not work because the cache + # would be repopulated by a node that has not been upgraded. + def self.remove_legacy_cache_key + Rails.cache.delete(LEGACY_CACHE_KEY) + end + def active? started? && !ended? end @@ -68,5 +103,6 @@ class BroadcastMessage < ActiveRecord::Base def flush_redis_cache Rails.cache.delete(CACHE_KEY) + self.class.remove_legacy_cache_key end end diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb index af699eeebce..498996f4f80 100644 --- a/app/models/concerns/storage/legacy_namespace.rb +++ b/app/models/concerns/storage/legacy_namespace.rb @@ -4,6 +4,8 @@ module Storage module LegacyNamespace extend ActiveSupport::Concern + include Gitlab::ShellAdapter + def move_dir proj_with_tags = first_project_with_container_registry_tags diff --git a/app/models/event.rb b/app/models/event.rb index 2e690f8c013..2ceef412af5 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -87,7 +87,7 @@ class Event < ActiveRecord::Base scope :with_associations, -> do # We're using preload for "push_event_payload" as otherwise the association # is not always available (depending on the query being built). - includes(:author, :project, project: :namespace) + includes(:author, :project, project: [:project_feature, :import_data, :namespace]) .preload(:target, :push_event_payload) end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 537f2a3a231..016c18ce6c8 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -3,8 +3,6 @@ class ProjectMember < Member SOURCE_TYPE = 'Project'.freeze - include Gitlab::ShellAdapter - belongs_to :project, foreign_key: 'source_id' # Make sure project member points only to project as it source diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 861211ffc0a..77e48ce11e8 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -978,6 +978,7 @@ class MergeRequest < ActiveRecord::Base def mergeable_ci_state? return true unless project.only_allow_merge_if_pipeline_succeeds? + return true unless head_pipeline actual_head_pipeline&.success? || actual_head_pipeline&.skipped? end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 8865c164b11..3c9b1d32a53 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -3,7 +3,6 @@ class Namespace < ActiveRecord::Base include CacheMarkdownField include Sortable - include Gitlab::ShellAdapter include Gitlab::VisibilityLevel include Routable include AfterCommitQueue diff --git a/app/models/note.rb b/app/models/note.rb index a6ae4f58ac4..17c7d97fa0a 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -131,7 +131,7 @@ class Note < ActiveRecord::Base scope :with_associations, -> do # FYI noteable cannot be loaded for LegacyDiffNote for commits includes(:author, :noteable, :updated_by, - project: [:project_members, { group: [:group_members] }]) + project: [:project_members, :namespace, { group: [:group_members] }]) end scope :with_metadata, -> { includes(:system_note_metadata) } diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 6c1073265a1..d075440b147 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ProtectedBranch < ActiveRecord::Base - include Gitlab::ShellAdapter include ProtectedRef protected_ref_access_levels :merge, :push diff --git a/app/models/protected_tag.rb b/app/models/protected_tag.rb index 94746141945..d28ebabfe49 100644 --- a/app/models/protected_tag.rb +++ b/app/models/protected_tag.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ProtectedTag < ActiveRecord::Base - include Gitlab::ShellAdapter include ProtectedRef validates :name, uniqueness: { scope: :project_id } diff --git a/app/models/repository.rb b/app/models/repository.rb index 35dd120856d..015a179f374 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -17,7 +17,6 @@ class Repository #{REF_ENVIRONMENTS} ].freeze - include Gitlab::ShellAdapter include Gitlab::RepositoryCacheAdapter attr_accessor :full_path, :disk_path, :project, :is_wiki diff --git a/app/services/clusters/build_service.rb b/app/services/clusters/build_service.rb new file mode 100644 index 00000000000..8de73831164 --- /dev/null +++ b/app/services/clusters/build_service.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +module Clusters + class BuildService + def initialize(subject) + @subject = subject + end + + def execute + ::Clusters::Cluster.new.tap do |cluster| + case @subject + when ::Project + cluster.cluster_type = :project_type + when ::Group + cluster.cluster_type = :group_type + else + raise NotImplementedError + end + end + end + end +end diff --git a/app/services/clusters/gcp/fetch_operation_service.rb b/app/services/clusters/gcp/fetch_operation_service.rb index 02c96a1e286..6c648b443a0 100644 --- a/app/services/clusters/gcp/fetch_operation_service.rb +++ b/app/services/clusters/gcp/fetch_operation_service.rb @@ -11,8 +11,21 @@ module Clusters yield(operation) if block_given? rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e + logger.error( + exception: e.class.name, + service: self.class.name, + provider_id: provider.id, + message: e.message + ) + provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") end + + private + + def logger + @logger ||= Gitlab::Kubernetes::Logger.build + end end end end diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index e029323774c..301059f0326 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -16,10 +16,13 @@ module Clusters ClusterPlatformConfigureWorker.perform_async(cluster.id) rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e + log_service_error(e.class.name, provider.id, e.message) provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") rescue Kubeclient::HttpError => e + log_service_error(e.class.name, provider.id, e.message) provider.make_errored!("Failed to run Kubeclient: #{e.message}") rescue ActiveRecord::RecordInvalid => e + log_service_error(e.class.name, provider.id, e.message) provider.make_errored!("Failed to configure Google Kubernetes Engine Cluster: #{e.message}") end @@ -105,6 +108,19 @@ module Clusters def cluster @cluster ||= provider.cluster end + + def logger + @logger ||= Gitlab::Kubernetes::Logger.build + end + + def log_service_error(exception, provider_id, message) + logger.error( + exception: exception.class.name, + service: self.class.name, + provider_id: provider_id, + message: message + ) + end end end end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 7ac79cc77f5..6756299cf43 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -174,7 +174,7 @@ %h4 Latest projects - @projects.each do |project| %p - = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60' + = link_to project.full_name, admin_project_path(project), class: 'str-truncated-60' %span.light.float-right #{time_ago_with_tooltip(project.created_at)} .col-md-4 diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 5f205d1bcbc..da2ebb08405 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -101,7 +101,7 @@ = _('Add user(s) to the group:') .card-body.form-holder %p.light - - link_to_help = link_to(_("here"), help_page_path("user/permissions"), class: "vlink") + - link_to_help = link_to(_("here"), help_page_path("user/permissions")) = _('Read more about project permissions <strong>%{link_to_help}</strong>').html_safe % { link_to_help: link_to_help } = form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml index 486d0477f20..9c6c74ed965 100644 --- a/app/views/admin/hooks/edit.html.haml +++ b/app/views/admin/hooks/edit.html.haml @@ -4,7 +4,7 @@ Edit System Hook %p.light - #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be + #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks')} can be used for binding events when GitLab creates a User or Project. %hr diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 5d462d7b732..b65bf07160a 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -4,7 +4,7 @@ %h4.prepend-top-0 = page_title %p - #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be + #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks')} can be used for binding events when GitLab creates a User or Project. .col-lg-8.append-bottom-default diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 04683ec5a9a..c8cdc2cc3e4 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -8,7 +8,7 @@ .col-md-3.col-lg-2 = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select" .form-text.text-muted.append-bottom-10 - = link_to "Read more", help_page_path("user/permissions"), class: "vlink" + = link_to "Read more", help_page_path("user/permissions") about role permissions .col-md-3.col-lg-2 diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index b7d69539eb7..e8d0d809181 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -15,7 +15,7 @@ = brand_header_logo - logo_text = brand_header_logo_type - if logo_text.present? - %span.logo-text.d-none.d-sm-block + %span.logo-text.d-none.d-lg-block.prepend-left-8 = logo_text - if current_user diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index ea5f2b166b4..7057a5a142f 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,3 +1,5 @@ +-# WAIT! Before adding more items to the nav bar, please see +-# https://gitlab.com/gitlab-org/gitlab-ce/issues/49713 for more information. %ul.list-unstyled.navbar-sub-nav - if dashboard_nav_link?(:projects) = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown" }) do @@ -16,22 +18,22 @@ = render "layouts/nav/groups_dropdown/show" - if dashboard_nav_link?(:activity) - = nav_link(path: 'dashboard#activity', html_options: { class: "d-none d-lg-block d-xl-block" }) do + = nav_link(path: 'dashboard#activity', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: _('Activity') do = _('Activity') - if dashboard_nav_link?(:milestones) - = nav_link(controller: 'dashboard/milestones', html_options: { class: "d-none d-lg-block d-xl-block" }) do + = nav_link(controller: 'dashboard/milestones', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: _('Milestones') do = _('Milestones') - if dashboard_nav_link?(:snippets) - = nav_link(controller: 'dashboard/snippets', html_options: { class: "d-none d-lg-block d-xl-block" }) do + = nav_link(controller: 'dashboard/snippets', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: _('Snippets') do = _('Snippets') - if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets]) - %li.header-more.dropdown.d-lg-none.d-xl-none + %li.header-more.dropdown.d-xl-none{ class: ('d-lg-none' unless has_extra_nav_icons?) } %a{ href: "#", data: { toggle: "dropdown" } } = _('More') = sprite_icon('angle-down', css_class: 'caret-down') @@ -52,6 +54,21 @@ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: _('Snippets') do = _('Snippets') + = render_if_exists 'dashboard/operations/nav_link' + - if can?(current_user, :read_instance_statistics) + = nav_link(controller: [:conversational_development_index, :cohorts]) do + = link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = _('Instance Statistics') + - if current_user.admin? + = nav_link(controller: 'admin/dashboard') do + = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = _('Admin Area') + - if Gitlab::Sherlock.enabled? + %li + = link_to sherlock_transactions_path, class: 'admin-icon', title: _('Sherlock Transactions'), + data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = _('Sherlock Transactions') + -# Shortcut to Dashboard > Projects - if dashboard_nav_link?(:projects) %li.hidden @@ -64,19 +81,17 @@ = link_to '#', class: 'dashboard-shortcuts-web-ide', title: _('Web IDE') do = _('Web IDE') - - if show_separator? - %li.line-separator.d-none.d-sm-block = render_if_exists 'dashboard/operations/nav_link' - if can?(current_user, :read_instance_statistics) - = nav_link(controller: [:conversational_development_index, :cohorts]) do + = nav_link(controller: [:conversational_development_index, :cohorts], html_options: { class: "d-none d-lg-block d-xl-block"}) do = link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = sprite_icon('chart', size: 18) - if current_user.admin? - = nav_link(controller: 'admin/dashboard') do - = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin area'), aria: { label: _('Admin area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = nav_link(controller: 'admin/dashboard', html_options: { class: "d-none d-lg-block d-xl-block"}) do + = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = sprite_icon('admin', size: 18) - if Gitlab::Sherlock.enabled? %li - = link_to sherlock_transactions_path, class: 'admin-icon', title: _('Sherlock Transactions'), + = link_to sherlock_transactions_path, class: 'admin-icon d-none d-lg-block d-xl-block', title: _('Sherlock Transactions'), data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('tachometer fw') diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml index d82a3dd70f9..d453a3a9dac 100644 --- a/app/views/projects/buttons/_clone.html.haml +++ b/app/views/projects/buttons/_clone.html.haml @@ -10,12 +10,12 @@ %span.append-right-4.js-clone-dropdown-label = _('Clone') = sprite_icon("arrow-down", css_class: "icon") - %form.p-3.dropdown-menu.dropdown-menu-right.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown + %form.p-3.dropdown-menu.dropdown-menu-right.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown.qa-clone-options %li.pb-2 %label.label-bold = _('Clone with SSH') .input-group - = text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' } + = text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control qa-ssh-clone-url", readonly: true, aria: { label: 'Project clone URL' } .input-group-append = clipboard_button(target: '#ssh_project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard") = render_if_exists 'projects/buttons/geo' @@ -23,7 +23,7 @@ %label.label-bold = _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase } .input-group - = text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' } + = text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control qa-http-clone-url", readonly: true, aria: { label: 'Project clone URL' } .input-group-append = clipboard_button(target: '#http_project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard") = render_if_exists 'projects/buttons/geo' diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index aa690b12eb7..081990ac9b7 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -32,7 +32,7 @@ .prepend-top-20 %nav.project-buttons - .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller + .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.qa-quick-actions .fade-left= icon('angle-left') .fade-right= icon('angle-right') .nav-links.scrolling-tabs.quick-links diff --git a/app/views/projects/project_members/_new_project_group.html.haml b/app/views/projects/project_members/_new_project_group.html.haml index 74570769117..88e68f89024 100644 --- a/app/views/projects/project_members/_new_project_group.html.haml +++ b/app/views/projects/project_members/_new_project_group.html.haml @@ -10,7 +10,7 @@ = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control" = icon('chevron-down') .form-text.text-muted.append-bottom-10 - = link_to _("Read more"), help_page_path("user/permissions"), class: "vlink" + = link_to _("Read more"), help_page_path("user/permissions") about role permissions .form-group = label_tag :expires_at, _('Access expiration date'), class: 'label-bold' diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 5e21442bb60..1de7d9c6957 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -10,7 +10,7 @@ = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select select-control" = icon('chevron-down') .form-text.text-muted.append-bottom-10 - = link_to "Read more", help_page_path("user/permissions"), class: "vlink" + = link_to "Read more", help_page_path("user/permissions") about role permissions .form-group .clearable-input diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index a89df6adfb3..4e9a119ac66 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -20,7 +20,7 @@ - if can_collaborate || can_create_mr_from_fork %li.breadcrumb-item - %a.btn.add-to-tree{ addtotree_toggle_attributes } + %a.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes } = sprite_icon('plus', size: 16, css_class: 'float-left') = sprite_icon('arrow-down', size: 16, css_class: 'float-left') - if on_top_of_branch? @@ -30,7 +30,7 @@ %li.dropdown-header #{ _('This directory') } %li - = link_to project_new_blob_path(@project, @id) do + = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do #{ _('New file') } %li = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 5295e656ab0..9eecfa39390 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -16,7 +16,7 @@ - if current_user .block.todo.hide-expanded = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true - .block.assignee + .block.assignee.qa-assignee-block = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present? = render_if_exists 'shared/issuable/sidebar_item_epic', issuable: issuable diff --git a/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml b/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml index 3521f71f409..60c34094108 100644 --- a/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml +++ b/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml @@ -5,4 +5,4 @@ = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) - = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignee_id == current_user.id}" + = link_to 'Assign to me', '#', class: "assign-to-me-link qa-assign-to-me-link #{'hide' if issuable.assignee_id == current_user.id}" diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index d11476738e4..dd2cd36eac2 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -31,12 +31,12 @@ data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('users') - .profile-header + .profile-header{ class: [('with-no-profile-tabs' if profile_tabs.empty?)] } .avatar-holder = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = image_tag avatar_icon_for_user(@user, 90), class: "avatar s90", alt: '' - .user-info.prepend-left-default.append-right-default + .user-info .cover-title = @user.name @@ -81,10 +81,10 @@ = icon('briefcase') = @user.organization - - if @user.bio.present? - .cover-desc - %p.profile-user-bio - = @user.bio + - if @user.bio.present? + .cover-desc + %p.profile-user-bio + = @user.bio - unless profile_tabs.empty? .scrolling-tabs-container diff --git a/app/workers/repository_update_remote_mirror_worker.rb b/app/workers/repository_update_remote_mirror_worker.rb index 9d4e67deb9c..bd429d526bf 100644 --- a/app/workers/repository_update_remote_mirror_worker.rb +++ b/app/workers/repository_update_remote_mirror_worker.rb @@ -5,7 +5,6 @@ class RepositoryUpdateRemoteMirrorWorker UpdateError = Class.new(StandardError) include ApplicationWorker - include Gitlab::ShellAdapter sidekiq_options retry: 3, dead: false |