diff options
Diffstat (limited to 'app')
17 files changed, 197 insertions, 133 deletions
diff --git a/app/assets/javascripts/logs/components/environment_logs.vue b/app/assets/javascripts/logs/components/environment_logs.vue index 0d84798d690..838652f7210 100644 --- a/app/assets/javascripts/logs/components/environment_logs.vue +++ b/app/assets/javascripts/logs/components/environment_logs.vue @@ -89,10 +89,9 @@ export default { methods: { ...mapActions('environmentLogs', [ 'setInitData', - 'setSearch', - 'showPodLogs', 'showEnvironment', 'fetchEnvironments', + 'fetchLogs', 'fetchMoreLogsPrepend', 'dismissRequestEnvironmentsError', 'dismissInvalidTimeRangeWarning', @@ -191,13 +190,13 @@ export default { <log-advanced-filters v-if="showAdvancedFilters" ref="log-advanced-filters" - class="d-md-flex flex-grow-1" + class="d-md-flex flex-grow-1 min-width-0" :disabled="environments.isLoading" /> <log-simple-filters v-else ref="log-simple-filters" - class="d-md-flex flex-grow-1" + class="d-md-flex flex-grow-1 min-width-0" :disabled="environments.isLoading" /> @@ -205,7 +204,7 @@ export default { ref="scrollButtons" class="flex-grow-0 pr-2 mb-2 controllers" :scroll-down-button-disabled="scrollDownButtonDisabled" - @refresh="showPodLogs(pods.current)" + @refresh="fetchLogs()" @scrollDown="scrollDown" /> </div> diff --git a/app/assets/javascripts/logs/components/log_advanced_filters.vue b/app/assets/javascripts/logs/components/log_advanced_filters.vue index dfbd858bf18..49bb80b3bfd 100644 --- a/app/assets/javascripts/logs/components/log_advanced_filters.vue +++ b/app/assets/javascripts/logs/components/log_advanced_filters.vue @@ -1,25 +1,15 @@ <script> -import { s__ } from '~/locale'; -import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import { mapActions, mapState } from 'vuex'; -import { - GlIcon, - GlDropdown, - GlDropdownHeader, - GlDropdownDivider, - GlDropdownItem, - GlSearchBoxByClick, -} from '@gitlab/ui'; +import { GlFilteredSearch } from '@gitlab/ui'; +import { __, s__ } from '~/locale'; +import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import { timeRanges } from '~/vue_shared/constants'; +import { TOKEN_TYPE_POD_NAME } from '../constants'; +import TokenWithLoadingState from './tokens/token_with_loading_state.vue'; export default { components: { - GlIcon, - GlDropdown, - GlDropdownHeader, - GlDropdownDivider, - GlDropdownItem, - GlSearchBoxByClick, + GlFilteredSearch, DateTimePicker, }, props: { @@ -32,11 +22,10 @@ export default { data() { return { timeRanges, - searchQuery: '', }; }, computed: { - ...mapState('environmentLogs', ['timeRange', 'pods']), + ...mapState('environmentLogs', ['timeRange', 'pods', 'logs']), timeRangeModel: { get() { @@ -46,75 +35,56 @@ export default { this.setTimeRange(val); }, }, + /** + * Token options. + * + * Returns null when no pods are present, so suggestions are displayed in the token + */ + podOptions() { + if (this.pods.options.length) { + return this.pods.options.map(podName => ({ value: podName, title: podName })); + } + return null; + }, - podDropdownText() { - return this.pods.current || s__('Environments|All pods'); + tokens() { + return [ + { + icon: 'pod', + type: TOKEN_TYPE_POD_NAME, + title: s__('Environments|Pod name'), + token: TokenWithLoadingState, + operators: [{ value: '=', description: __('is'), default: 'true' }], + unique: true, + options: this.podOptions, + loading: this.logs.isLoading, + noOptionsText: s__('Environments|No pods to display'), + }, + ]; }, }, methods: { - ...mapActions('environmentLogs', ['setSearch', 'showPodLogs', 'setTimeRange']), - isCurrentPod(podName) { - return podName === this.pods.current; + ...mapActions('environmentLogs', ['showFilteredLogs', 'setTimeRange']), + + filteredSearchSubmit(filters) { + this.showFilteredLogs(filters); }, }, }; </script> <template> <div> - <gl-dropdown - ref="podsDropdown" - :text="podDropdownText" - :disabled="disabled" - class="mb-2 gl-h-32 pr-2 d-flex d-md-block flex-grow-0 qa-pods-dropdown" - > - <gl-dropdown-header class="text-center"> - {{ s__('Environments|Filter by pod') }} - </gl-dropdown-header> - - <gl-dropdown-item v-if="!pods.options.length" disabled> - <span ref="noPodsMsg" class="text-muted"> - {{ s__('Environments|No pods to display') }} - </span> - </gl-dropdown-item> - - <template v-else> - <gl-dropdown-item ref="allPodsOption" key="all-pods" @click="showPodLogs(null)"> - <div class="d-flex"> - <gl-icon - :class="{ invisible: pods.current !== null }" - name="status_success_borderless" - /> - <div class="flex-grow-1">{{ s__('Environments|All pods') }}</div> - </div> - </gl-dropdown-item> - <gl-dropdown-divider /> - <gl-dropdown-item - v-for="podName in pods.options" - :key="podName" - class="text-nowrap" - @click="showPodLogs(podName)" - > - <div class="d-flex"> - <gl-icon - :class="{ invisible: !isCurrentPod(podName) }" - name="status_success_borderless" - /> - <div class="flex-grow-1">{{ podName }}</div> - </div> - </gl-dropdown-item> - </template> - </gl-dropdown> - - <gl-search-box-by-click - ref="searchBox" - v-model.trim="searchQuery" - :disabled="disabled" - :placeholder="s__('Environments|Search')" - class="mb-2 pr-2 flex-grow-1" - type="search" - autofocus - @submit="setSearch(searchQuery)" - /> + <div class="mb-2 pr-2 flex-grow-1 min-width-0"> + <gl-filtered-search + :placeholder="__('Search')" + :clear-button-title="__('Clear')" + :close-button-title="__('Close')" + class="gl-h-32" + :disabled="disabled || logs.isLoading" + :available-tokens="tokens" + @submit="filteredSearchSubmit" + /> + </div> <date-time-picker ref="dateTimePicker" diff --git a/app/assets/javascripts/logs/components/tokens/token_with_loading_state.vue b/app/assets/javascripts/logs/components/tokens/token_with_loading_state.vue new file mode 100644 index 00000000000..f8ce704942b --- /dev/null +++ b/app/assets/javascripts/logs/components/tokens/token_with_loading_state.vue @@ -0,0 +1,30 @@ +<script> +import { GlFilteredSearchToken, GlLoadingIcon } from '@gitlab/ui'; + +export default { + components: { + GlFilteredSearchToken, + GlLoadingIcon, + }, + inheritAttrs: false, + props: { + config: { + type: Object, + required: true, + }, + }, +}; +</script> + +<template> + <gl-filtered-search-token :config="config" v-bind="{ ...$attrs }" v-on="$listeners"> + <template #suggestions> + <div class="m-1"> + <gl-loading-icon v-if="config.loading" /> + <div v-else class="py-1 px-2 text-muted"> + {{ config.noOptionsText }} + </div> + </div> + </template> + </gl-filtered-search-token> +</template> diff --git a/app/assets/javascripts/logs/constants.js b/app/assets/javascripts/logs/constants.js new file mode 100644 index 00000000000..450b83f4827 --- /dev/null +++ b/app/assets/javascripts/logs/constants.js @@ -0,0 +1,3 @@ +export const dateFormatMask = 'UTC:mmm dd HH:MM:ss.l"Z"'; + +export const TOKEN_TYPE_POD_NAME = 'TOKEN_TYPE_POD_NAME'; diff --git a/app/assets/javascripts/logs/stores/actions.js b/app/assets/javascripts/logs/stores/actions.js index be847108a49..a86d3c775a9 100644 --- a/app/assets/javascripts/logs/stores/actions.js +++ b/app/assets/javascripts/logs/stores/actions.js @@ -2,6 +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 } from '../constants'; import * as types from './mutation_types'; @@ -49,19 +50,42 @@ const requestLogsUntilData = ({ commit, state }) => { return requestUntilData(logs_api_path, params); }; +/** + * Converts filters emitted by the component, e.g. a filterered-search + * to parameters to be applied to the filters of the store + * @param {Array} filters - List of strings or objects to filter by. + * @returns {Object} - An object with `search` and `podName` keys. + */ +const filtersToParams = (filters = []) => { + // Strings become part of the `search` + const search = filters + .filter(f => typeof f === 'string') + .join(' ') + .trim(); + + // null podName to show all pods + const podName = filters.find(f => f?.type === TOKEN_TYPE_POD_NAME)?.value?.data ?? null; + + return { search, podName }; +}; + export const setInitData = ({ commit }, { timeRange, environmentName, podName }) => { commit(types.SET_TIME_RANGE, timeRange); commit(types.SET_PROJECT_ENVIRONMENT, environmentName); commit(types.SET_CURRENT_POD_NAME, podName); }; -export const showPodLogs = ({ dispatch, commit }, podName) => { +export const showFilteredLogs = ({ dispatch, commit }, filters = []) => { + const { podName, search } = filtersToParams(filters); + commit(types.SET_CURRENT_POD_NAME, podName); + commit(types.SET_SEARCH, search); + dispatch('fetchLogs'); }; -export const setSearch = ({ dispatch, commit }, searchQuery) => { - commit(types.SET_SEARCH, searchQuery); +export const showPodLogs = ({ dispatch, commit }, podName) => { + commit(types.SET_CURRENT_POD_NAME, podName); dispatch('fetchLogs'); }; diff --git a/app/assets/javascripts/logs/utils.js b/app/assets/javascripts/logs/utils.js index 30213dbc130..8479eeb3b59 100644 --- a/app/assets/javascripts/logs/utils.js +++ b/app/assets/javascripts/logs/utils.js @@ -1,7 +1,6 @@ import { secondsToMilliseconds } from '~/lib/utils/datetime_utility'; import dateFormat from 'dateformat'; - -const dateFormatMask = 'UTC:mmm dd HH:MM:ss.l"Z"'; +import { dateFormatMask } from './constants'; /** * Returns a time range (`start`, `end`) where `start` is the diff --git a/app/assets/javascripts/static_site_editor/components/saved_changes_message.vue b/app/assets/javascripts/static_site_editor/components/saved_changes_message.vue index adcacf8a1b0..d76c6d9d681 100644 --- a/app/assets/javascripts/static_site_editor/components/saved_changes_message.vue +++ b/app/assets/javascripts/static_site_editor/components/saved_changes_message.vue @@ -36,7 +36,7 @@ export default { <template> <div> - <div> + <div class="border-bottom pb-4"> <h3>{{ s__('StaticSiteEditor|Success!') }}</h3> <p> {{ @@ -45,35 +45,37 @@ export default { ) }} </p> - <div> + <div class="d-flex justify-content-end"> <gl-new-button ref="returnToSiteButton" :href="returnUrl">{{ s__('StaticSiteEditor|Return to site') }}</gl-new-button> - <gl-new-button ref="mergeRequestButton" :href="mergeRequest.url" variant="info">{{ - s__('StaticSiteEditor|View merge request') - }}</gl-new-button> + <gl-new-button + ref="mergeRequestButton" + class="ml-2" + :href="mergeRequest.url" + variant="success" + >{{ s__('StaticSiteEditor|View merge request') }}</gl-new-button + > </div> </div> - <hr /> - - <div> + <div class="pt-2"> <h4>{{ s__('StaticSiteEditor|Summary of changes') }}</h4> <ul> <li> - {{ s__('StaticSiteEditor|A new branch was created:') }} + {{ s__('StaticSiteEditor|You created a new branch:') }} <gl-link ref="branchLink" :href="branch.url">{{ branch.label }}</gl-link> </li> <li> - {{ s__('StaticSiteEditor|Your changes were committed to it:') }} - <gl-link ref="commitLink" :href="commit.url">{{ commit.label }}</gl-link> - </li> - <li> - {{ s__('StaticSiteEditor|A merge request was created:') }} + {{ s__('StaticSiteEditor|You created a merge request:') }} <gl-link ref="mergeRequestLink" :href="mergeRequest.url">{{ mergeRequest.label }}</gl-link> </li> + <li> + {{ s__('StaticSiteEditor|You added a commit:') }} + <gl-link ref="commitLink" :href="commit.url">{{ commit.label }}</gl-link> + </li> </ul> </div> </div> diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 9a473876fa0..5f6a26d0a14 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -96,8 +96,8 @@ } .name { - background-color: $filter-name-resting-color; - color: $filter-name-text-color; + background-color: $white-normal; + color: $gl-text-color-secondary; border-radius: 2px 0 0 2px; margin-right: 1px; text-transform: capitalize; @@ -105,7 +105,7 @@ .operator { background-color: $white-normal; - color: $filter-value-text-color; + color: $gl-text-color; margin-right: 1px; } @@ -113,7 +113,7 @@ display: flex; align-items: center; background-color: $white-normal; - color: $filter-value-text-color; + color: $gl-text-color; border-radius: 0 2px 2px 0; margin-right: 5px; padding-right: 8px; @@ -152,7 +152,7 @@ .filtered-search-token .selected, .filtered-search-term .selected { .name { - background-color: $filter-name-selected-color; + background-color: $gray-200; } .operator { diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 69aed2fc20a..816dbc6931c 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -86,13 +86,13 @@ line-height: 10px; color: $gl-gray-700; vertical-align: middle; - background-color: $kdb-bg; + background-color: $gray-50; border-width: 1px; border-style: solid; - border-color: $gl-gray-200 $gl-gray-200 $kdb-border-bottom; + border-color: $gray-200 $gray-200 $gray-400; border-image: none; border-radius: 3px; - box-shadow: 0 -1px 0 $kdb-shadow inset; + box-shadow: 0 -1px 0 $gray-400 inset; } h1 { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 65efbabaa4f..fed2b5c905d 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -485,7 +485,7 @@ $line-removed-dark: #fac5cd; $line-number-old: #f9d7dc; $line-number-new: #ddfbe6; $line-number-select: #fbf2da; -$line-target-blue: #f6faff; +$line-target-blue: $blue-50; $line-select-yellow: #fcf8e7; $line-select-yellow-dark: #f0e2bd; $dark-diff-match-bg: rgba(255, 255, 255, 0.3); @@ -698,7 +698,7 @@ $logs-p-color: #333; */ $input-height: 34px; $input-danger-bg: #f2dede; -$input-group-addon-bg: #f7f8fa; +$input-group-addon-bg: $gray-50; $gl-field-focus-shadow: rgba(0, 0, 0, 0.075); $gl-field-focus-shadow-error: rgba($red-500, 0.6); $input-short-width: 200px; @@ -774,9 +774,6 @@ $select2-drop-shadow2: rgba(31, 37, 50, 0.317647); /* * Typography */ -$kdb-bg: #fcfcfc; -$kdb-border-bottom: #bbb; -$kdb-shadow: #bbb; $body-text-shadow: rgba(255, 255, 255, 0.01); /* @@ -801,20 +798,6 @@ CI variable lists $ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding}); /* -Filtered Search -*/ -$filter-name-resting-color: #f8f8f8; -$filter-name-text-color: rgba(0, 0, 0, 0.55); -$filter-value-text-color: rgba(0, 0, 0, 0.85); -$filter-name-selected-color: #ebebeb; -$filter-value-selected-color: #d7d7d7; - -/* -Animation Functions -*/ -$dropdown-animation-timing: cubic-bezier(0.23, 1, 0.32, 1); - -/* GitLab Plans */ $gl-gold-plan: #d4af37; diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 91bed4fc9f2..2a811e08fd3 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -54,6 +54,11 @@ .mh-50vh { max-height: 50vh; } +.min-width-0 { + // By default flex items don't shrink below their minimum content size. To change this, set the item's min-width + min-width: 0; +} + .font-size-inherit { font-size: inherit; } .gl-w-8 { width: px-to-rem($grid-size); } .gl-w-16 { width: px-to-rem($grid-size * 2); } diff --git a/app/models/group.rb b/app/models/group.rb index 203ed1694b7..f4eaa581d54 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -475,6 +475,16 @@ class Group < Namespace false end + def wiki_access_level + # TODO: Remove this method once we implement group-level features. + # https://gitlab.com/gitlab-org/gitlab/-/issues/208412 + if Feature.enabled?(:group_wiki, self) + ProjectFeature::ENABLED + else + ProjectFeature::DISABLED + end + end + private def update_two_factor_requirement diff --git a/app/policies/project_policy/class_methods.rb b/app/policies/concerns/crud_policy_helpers.rb index 42d993406a9..d8521ca22cc 100644 --- a/app/policies/project_policy/class_methods.rb +++ b/app/policies/concerns/crud_policy_helpers.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -class ProjectPolicy - module ClassMethods +module CrudPolicyHelpers + extend ActiveSupport::Concern + + class_methods do def create_read_update_admin_destroy(name) [ :"read_#{name}", diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 5e252c8e564..a34217d90dd 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class GroupPolicy < BasePolicy + include CrudPolicyHelpers include FindGroupProjects desc "Group is public" @@ -42,15 +43,23 @@ class GroupPolicy < BasePolicy @subject.subgroup_creation_level == ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS end + desc "Group has wiki disabled" + condition(:wiki_disabled, score: 32) { !feature_available?(:wiki) } + rule { public_group }.policy do enable :read_group enable :read_package + enable :read_wiki end - rule { logged_in_viewable }.enable :read_group + rule { logged_in_viewable }.policy do + enable :read_group + enable :read_wiki + end rule { guest }.policy do enable :read_group + enable :read_wiki enable :upload_file end @@ -78,10 +87,12 @@ class GroupPolicy < BasePolicy enable :create_metrics_dashboard_annotation enable :delete_metrics_dashboard_annotation enable :update_metrics_dashboard_annotation + enable :create_wiki end rule { reporter }.policy do enable :read_container_image + enable :download_wiki_code enable :admin_label enable :admin_list enable :admin_issue @@ -100,6 +111,7 @@ class GroupPolicy < BasePolicy enable :destroy_deploy_token enable :read_deploy_token enable :create_deploy_token + enable :admin_wiki end rule { owner }.policy do @@ -145,6 +157,11 @@ class GroupPolicy < BasePolicy rule { maintainer & can?(:create_projects) }.enable :transfer_projects + rule { wiki_disabled }.policy do + prevent(*create_read_update_admin_destroy(:wiki)) + prevent(:download_wiki_code) + end + def access_level return GroupMember::NO_ACCESS if @user.nil? @@ -154,6 +171,21 @@ class GroupPolicy < BasePolicy def lookup_access_level! @subject.max_member_access_for_user(@user) end + + # TODO: Extract this into a helper shared with ProjectPolicy, once we implement group-level features. + # https://gitlab.com/gitlab-org/gitlab/-/issues/208412 + def feature_available?(feature) + return false unless feature == :wiki + + case @subject.wiki_access_level + when ProjectFeature::DISABLED + false + when ProjectFeature::PRIVATE + admin? || access_level >= ProjectFeature.required_minimum_access_level(feature) + else + true + end + end end GroupPolicy.prepend_if_ee('EE::GroupPolicy') diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb index f86892227df..20df823c737 100644 --- a/app/policies/issue_policy.rb +++ b/app/policies/issue_policy.rb @@ -5,7 +5,7 @@ class IssuePolicy < IssuablePolicy # Make sure to sync this class checks with issue.rb to avoid security problems. # Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information. - extend ProjectPolicy::ClassMethods + include CrudPolicyHelpers desc "User can read confidential issues" condition(:can_read_confidential) do diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 0f5e4ac378e..7454343a357 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ProjectPolicy < BasePolicy - extend ClassMethods + include CrudPolicyHelpers READONLY_FEATURES_WHEN_ARCHIVED = %i[ issue diff --git a/app/services/prometheus/create_default_alerts_service.rb b/app/services/prometheus/create_default_alerts_service.rb index 3eb5ad7711a..c87cbbbe3cf 100644 --- a/app/services/prometheus/create_default_alerts_service.rb +++ b/app/services/prometheus/create_default_alerts_service.rb @@ -16,6 +16,11 @@ module Prometheus identifier: 'response_metrics_nginx_ingress_http_error_rate', operator: 'gt', threshold: 0.1 + }, + { + identifier: 'response_metrics_nginx_http_error_percentage', + operator: 'gt', + threshold: 0.1 } ].freeze |