Commit 707742e5 authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖
Browse files

Add latest changes from gitlab-org/gitlab@master

parent a0834ebc
......@@ -41,10 +41,6 @@ export default {
return !this.hideFileStats && this.file.type === 'blob';
},
fileClasses() {
if (!this.glFeatures.highlightCurrentDiffRow) {
return '';
}
return this.file.type === 'blob' && !this.viewedFiles[this.file.fileHash]
? 'gl-font-weight-bold'
: '';
......
<script>
import { mapState } from 'vuex';
import MembersFilteredSearchBar from './members_filtered_search_bar.vue';
import SortDropdown from './sort_dropdown.vue';
export default {
name: 'FilterSortContainer',
components: { MembersFilteredSearchBar },
components: { MembersFilteredSearchBar, SortDropdown },
computed: {
...mapState(['filteredSearchBar']),
...mapState(['filteredSearchBar', 'tableSortableFields']),
showContainer() {
return this.filteredSearchBar.show || this.showSortDropdown;
},
showSortDropdown() {
return this.tableSortableFields.length;
},
},
};
</script>
<template>
<div v-if="filteredSearchBar.show" class="gl-bg-gray-10 gl-p-5">
<members-filtered-search-bar />
<div v-if="showContainer" class="gl-bg-gray-10 gl-p-3 gl-display-md-flex">
<members-filtered-search-bar v-if="filteredSearchBar.show" class="gl-p-3 gl-flex-grow-1" />
<sort-dropdown v-if="showSortDropdown" class="gl-p-3 gl-flex-shrink-0" />
</div>
</template>
<script>
import { mapState } from 'vuex';
import { GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui';
import { parseSortParam, buildSortUrl } from '~/members/utils';
import { FIELDS } from '~/members/constants';
export default {
name: 'SortDropdown',
components: { GlDropdown, GlDropdownItem, GlFormGroup },
computed: {
...mapState(['tableSortableFields', 'filteredSearchBar']),
sort() {
return parseSortParam(this.tableSortableFields);
},
filteredOptions() {
const buildOption = (field, sortDesc) => ({
...(sortDesc ? field.sort.desc : field.sort.asc),
key: field.key,
sortDesc,
url: buildSortUrl({
sortBy: field.key,
sortDesc,
filteredSearchBarTokens: this.filteredSearchBar.tokens,
filteredSearchBarSearchParam: this.filteredSearchBar.searchParam,
}),
});
return FIELDS.filter(
field => this.tableSortableFields.includes(field.key) && field.sort,
).flatMap(field => [buildOption(field, false), buildOption(field, true)]);
},
},
methods: {
isChecked(key, sortDesc) {
return this.sort?.sortBy === key && this.sort?.sortDesc === sortDesc;
},
},
};
</script>
<template>
<gl-form-group
:label="__('Sort by')"
class="gl-mb-0"
label-cols="auto"
label-class="gl-align-self-center gl-pb-0!"
>
<gl-dropdown
:text="sort.sortByLabel"
block
toggle-class="gl-mb-0"
data-testid="members-sort-dropdown"
right
>
<gl-dropdown-item
v-for="option in filteredOptions"
:key="option.param"
:href="option.url"
is-check-item
:is-checked="isChecked(option.key, option.sortDesc)"
>
{{ option.label }}
</gl-dropdown-item>
</gl-dropdown>
</gl-form-group>
</template>
import { __ } from '~/locale';
import { __, s__ } from '~/locale';
const ACCOUNT_SORT_ASC_LABEL = s__('Members|Account, ascending');
export const FIELDS = [
{
key: 'account',
label: __('Account'),
sort: {
asc: {
param: 'name_asc',
label: ACCOUNT_SORT_ASC_LABEL,
},
desc: {
param: 'name_desc',
label: s__('Members|Account, descending'),
},
},
},
{
key: 'source',
......@@ -16,6 +28,16 @@ export const FIELDS = [
label: __('Access granted'),
thClass: 'col-meta',
tdClass: 'col-meta',
sort: {
asc: {
param: 'last_joined',
label: s__('Members|Access granted, ascending'),
},
desc: {
param: 'oldest_joined',
label: s__('Members|Access granted, descending'),
},
},
},
{
key: 'invited',
......@@ -40,6 +62,16 @@ export const FIELDS = [
label: __('Max role'),
thClass: 'col-max-role',
tdClass: 'col-max-role',
sort: {
asc: {
param: 'access_level_asc',
label: s__('Members|Max role, ascending'),
},
desc: {
param: 'access_level_desc',
label: s__('Members|Max role, descending'),
},
},
},
{
key: 'expiration',
......@@ -47,6 +79,19 @@ export const FIELDS = [
thClass: 'col-expiration',
tdClass: 'col-expiration',
},
{
key: 'lastSignIn',
sort: {
asc: {
param: 'recent_sign_in',
label: s__('Members|Last sign-in, ascending'),
},
desc: {
param: 'oldest_sign_in',
label: s__('Members|Last sign-in, descending'),
},
},
},
{
key: 'actions',
thClass: 'col-actions',
......@@ -55,6 +100,12 @@ export const FIELDS = [
},
];
export const DEFAULT_SORT = {
sortBy: 'account',
sortDesc: false,
sortByLabel: ACCOUNT_SORT_ASC_LABEL,
};
export const AVATAR_SIZE = 48;
export const MEMBER_TYPES = {
......
import { __ } from '~/locale';
import { getParameterByName } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { FIELDS, DEFAULT_SORT } from './constants';
export const generateBadges = (member, isCurrentUser) => [
{
......@@ -44,5 +47,54 @@ export const canUpdate = (member, currentUserId, sourceId) => {
);
};
export const parseSortParam = sortableFields => {
const sortParam = getParameterByName('sort');
const sortedField = FIELDS.filter(field => sortableFields.includes(field.key)).find(
field => field.sort?.asc?.param === sortParam || field.sort?.desc?.param === sortParam,
);
if (!sortedField) {
return DEFAULT_SORT;
}
const isDesc = sortedField?.sort?.desc?.param === sortParam;
return {
sortBy: sortedField.key,
sortDesc: isDesc,
sortByLabel: isDesc ? sortedField?.sort?.desc?.label : sortedField?.sort?.asc?.label,
};
};
export const buildSortUrl = ({
sortBy,
sortDesc,
filteredSearchBarTokens,
filteredSearchBarSearchParam,
}) => {
const sortDefinition = FIELDS.find(field => field.key === sortBy)?.sort;
if (!sortDefinition) {
return '';
}
const sortParam = sortDesc ? sortDefinition.desc.param : sortDefinition.asc.param;
const filterParams =
filteredSearchBarTokens?.reduce((accumulator, token) => {
return {
...accumulator,
[token]: getParameterByName(token),
};
}, {}) || {};
if (filteredSearchBarSearchParam) {
filterParams[filteredSearchBarSearchParam] = getParameterByName(filteredSearchBarSearchParam);
}
return setUrlParams({ ...filterParams, sort: sortParam }, window.location.href, true);
};
// Defined in `ee/app/assets/javascripts/vue_shared/components/members/utils.js`
export const canOverride = () => false;
......@@ -36,7 +36,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true)
push_frontend_feature_flag(:merge_request_widget_graphql, @project)
push_frontend_feature_flag(:unified_diff_components, @project)
push_frontend_feature_flag(:highlight_current_diff_row, @project)
push_frontend_feature_flag(:default_merge_ref_for_diffs, @project)
push_frontend_feature_flag(:core_security_mr_widget, @project, default_enabled: true)
push_frontend_feature_flag(:core_security_mr_widget_counts, @project)
......
......@@ -6,14 +6,14 @@ def remove_member_message(member, user: nil)
text = 'Are you sure you want to'
action =
if member.request?
if member.invite?
"revoke the invitation for #{member.invite_email} to join"
elsif member.request?
if member.user == user
'withdraw your access request for'
else
"deny #{member.user.name}'s request to join"
end
elsif member.invite?
"revoke the invitation for #{member.invite_email} to join"
else
if member.user
"remove #{member.user.name} from"
......
......@@ -8,7 +8,6 @@ class AlertPresenter < Gitlab::View::Presenter::Delegated
MARKDOWN_LINE_BREAK = " \n"
HORIZONTAL_LINE = "\n\n---\n\n"
INCIDENT_LABEL_NAME = ::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES[:title]
delegate :metrics_dashboard_url, :runbook, to: :parsed_payload
......@@ -48,7 +47,7 @@ def show_performance_dashboard_link?
end
def incident_issues_link
project_issues_url(project, label_name: INCIDENT_LABEL_NAME)
project_incidents_url(project)
end
def performance_dashboard_link
......
......@@ -73,22 +73,6 @@ def delete_milestone_total_issue_counter_cache(milestone)
Milestones::IssuesCountService.new(milestone).delete_cache
end
# Applies label "incident" (creates it if missing) to incident issues.
# Please use in "after" hooks only to ensure we are not appyling
# labels prematurely.
def add_incident_label(issue)
return unless issue.incident?
label = ::IncidentManagement::CreateIncidentLabelService
.new(project, current_user)
.execute
.payload[:label]
return if issue.label_ids.include?(label.id)
issue.labels << label
end
end
end
......
......@@ -49,6 +49,22 @@ def resolve_discussions_with_issue(issue)
def user_agent_detail_service
UserAgentDetailService.new(@issue, @request)
end
# Applies label "incident" (creates it if missing) to incident issues.
# For use in "after" hooks only to ensure we are not appyling
# labels prematurely.
def add_incident_label(issue)
return unless issue.incident?
label = ::IncidentManagement::CreateIncidentLabelService
.new(project, current_user)
.execute
.payload[:label]
return if issue.label_ids.include?(label.id)
issue.labels << label
end
end
end
......
......@@ -34,7 +34,6 @@ def before_update(issue, skip_spam_check: false)
end
def after_update(issue)
add_incident_label(issue)
IssuesChannel.broadcast_to(issue, event: 'updated') if Gitlab::ActionCable::Config.in_app? || Feature.enabled?(:broadcast_issue_updates, issue.project)
end
......
......@@ -20,8 +20,8 @@ def execute(source)
emails.each do |email|
next if existing_member?(source, email)
next if existing_invite?(source, email)
next if existing_request?(source, email)
if existing_user?(email)
add_existing_user_as_member(current_user, source, params, email)
......@@ -44,8 +44,7 @@ def invite_new_member_and_user(current_user, source, params, email)
access_level: params[:access_level],
invite_email: email,
created_by_id: current_user.id,
expires_at: params[:expires_at],
requested_at: Time.current.utc)
expires_at: params[:expires_at])
unless new_member.valid? && new_member.persisted?
errors[params[:email]] = new_member.errors.full_messages.to_sentence
......@@ -92,6 +91,17 @@ def existing_invite?(source, email)
false
end
def existing_request?(source, email)
existing_request = source.requesters.with_user_by_email(email).exists?
if existing_request
errors[email] = "Member cannot be invited because they already requested to join #{source.name}"
return true
end
false
end
def existing_user(email)
User.find_by_email(email)
end
......
......@@ -4,7 +4,9 @@ module Packages
class CreateEventService < BaseService
def execute
if Feature.enabled?(:collect_package_events_redis) && redis_event_name
unless guest?
if guest?
::Gitlab::UsageDataCounters::GuestPackageEventCounter.count(redis_event_name)
else
::Gitlab::UsageDataCounters::HLLRedisCounter.track_event(current_user.id, redis_event_name)
end
end
......
---
title: Tracks guest package events
merge_request: 48547
author:
type: added
---
title: Enable file tree highlighting by default
merge_request: 49356
author:
type: changed
---
title: Add different string encoding method in rack middleware
merge_request: 49044
author:
type: fixed
---
title: Resolve Members page 500 error after Invitation sent via API
merge_request: 48937
author:
type: fixed
---
title: Do not automatically reapply incident label after user removes it
merge_request: 49188
author:
type: fixed
---
name: highlight_current_diff_row
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27937
rollout_issue_url:
milestone: '13.4'
type: development
group: group::source code
default_enabled: false
......@@ -107,7 +107,7 @@ curl: (22) The requested URL returned error: 404
```
The HTTP exit code can help you diagnose the success or failure of your REST call.
## Authentication
Most API requests require authentication, or only return public data when
......@@ -591,7 +591,7 @@ We can call the API with `array` and `hash` types parameters as follows:
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
-d "import_sources[]=github" \
-d "import_sources[]=bitbucket" \
https://gitlab.example.com/api/v4/some_endpoint
"https://gitlab.example.com/api/v4/some_endpoint"
```
### `hash`
......@@ -605,7 +605,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
--form "file=@/path/to/somefile.txt"
--form "override_params[visibility]=private" \
--form "override_params[some_other_param]=some_value" \
https://gitlab.example.com/api/v4/projects/import
"https://gitlab.example.com/api/v4/projects/import"
```
### Array of hashes
......
......@@ -258,7 +258,7 @@ are paginated.
Read more on [pagination](README.md#pagination).
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://primary.example.com/api/v4/projects/7/audit_events
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://primary.example.com/api/v4/projects/7/audit_events"
```
Example response:
......@@ -318,7 +318,7 @@ GET /projects/:id/audit_events/:audit_event_id
| `audit_event_id` | integer | yes | The ID of the audit event |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://primary.example.com/api/v4/projects/7/audit_events/5
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://primary.example.com/api/v4/projects/7/audit_events/5"
```
Example response:
......
......@@ -635,7 +635,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| `all` | boolean | no | Return all statuses, not only the latest ones
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses"
```
Example response:
......
......@@ -103,7 +103,7 @@ GET /projects/:id/feature_flags/:feature_flag_name
| `feature_flag_name` | string | yes | The name of the feature flag. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/feature_flags/awesome_feature
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/feature_flags/awesome_feature"
```
Example response:
......
......@@ -36,7 +36,7 @@ GET /projects/:id/feature_flags
| `scope` | string | no | The condition of feature flags, one of: `enabled`, `disabled`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/feature_flags
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/feature_flags"
```
Example response:
......@@ -174,7 +174,7 @@ POST /projects/:id/feature_flags
| `scopes:strategies` | JSON | no | The [strategies](../operations/feature_flags.md#feature-flag-strategies) of the feature flag spec. |
```shell
curl https://gitlab.example.com/api/v4/projects/1/feature_flags \
curl "https://gitlab.example.com/api/v4/projects/1/feature_flags" \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-type: application/json" \
--data @- << EOF
......@@ -244,7 +244,7 @@ GET /projects/:id/feature_flags/:name
| `name` | string | yes | The name of the feature flag. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/feature_flags/new_live_trace
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/feature_flags/new_live_trace"
```
Example response:
......@@ -320,5 +320,5 @@ DELETE /projects/:id/feature_flags/:name
| `name` | string | yes | The name of the feature flag. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" --request DELETE https://gitlab.example.com/api/v4/projects/1/feature_flags/awesome_feature
curl --header "PRIVATE-TOKEN: <your_access_token>" --request DELETE "https://gitlab.example.com/api/v4/projects/1/feature_flags/awesome_feature"
```
......@@ -101,7 +101,7 @@ Example request:
```shell
curl --header 'Content-Type: application/json' --header "PRIVATE-TOKEN: <your_access_token>" \
--data '{ "freeze_start": "0 23 * * 5", "freeze_end": "0 7 * * 1", "cron_timezone": "UTC" }' \
--request POST https://gitlab.example.com/api/v4/projects/19/freeze_periods
--request POST "https://gitlab.example.com/api/v4/projects/19/freeze_periods"
```
Example response:
......
......@@ -29,7 +29,7 @@ Example:
```shell
GRAPHQL_TOKEN=<your-token>
curl 'https://gitlab.com/api/graphql' --header "Authorization: Bearer $GRAPHQL_TOKEN" --header "Content-Type: application/json" --request POST --data "{\"query\": \"query {currentUser {name}}\"}"
curl "https://gitlab.com/api/graphql" --header "Authorization: Bearer $GRAPHQL_TOKEN" --header "Content-Type: application/json" --request POST --data "{\"query\": \"query {currentUser {name}}\"}"
```