Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
96fb7f03bd
commit
3a5eccd3d0
|
|
@ -1067,11 +1067,7 @@
|
|||
"type": "string",
|
||||
"markdownDescription": "Determines the strategy for downloading and updating the cache. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#cachepolicy)",
|
||||
"default": "pull-push",
|
||||
"enum": [
|
||||
"pull",
|
||||
"push",
|
||||
"pull-push"
|
||||
]
|
||||
"pattern": "pull-push|pull|push|\\$\\w{1,255}"
|
||||
},
|
||||
"unprotect": {
|
||||
"type": "boolean",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
|
|||
|
||||
around_action :allow_gitaly_ref_name_caching
|
||||
|
||||
after_action :track_viewed_diffs_events, only: [:diffs_batch]
|
||||
after_action :track_viewed_diffs_events, only: [:diffs_batch, :diff_for_path]
|
||||
|
||||
urgency :low, [
|
||||
:show,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ class PopulateReleasesAccessLevelFromRepository < Gitlab::Database::Migration[2.
|
|||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
update_column_in_batches(
|
||||
update_column_in_batches( # rubocop: disable Migration/UpdateColumnInBatches
|
||||
:project_features,
|
||||
:releases_access_level,
|
||||
Arel.sql('repository_access_level')
|
||||
|
|
|
|||
|
|
@ -261,6 +261,39 @@ cache:
|
|||
key: $CI_JOB_NAME
|
||||
```
|
||||
|
||||
### Use a variable to control a job's cache policy
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/371480) in GitLab 16.1.
|
||||
|
||||
To reduce duplication of jobs where the only difference is the pull policy, you can use a [CI/CD variable](../variables/index.md).
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
conditional-policy:
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
variables:
|
||||
POLICY: pull-push
|
||||
- if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
||||
variables:
|
||||
POLICY: pull
|
||||
stage: build
|
||||
cache:
|
||||
key: gems
|
||||
policy: $POLICY
|
||||
paths:
|
||||
- vendor/bundle
|
||||
script:
|
||||
- echo "This job pulls and pushes the cache depending on the branch"
|
||||
- echo "Downloading dependencies..."
|
||||
```
|
||||
|
||||
In this example, the job's cache policy is:
|
||||
|
||||
- `pull-push` for changes to the default branch.
|
||||
- `pull` for changes to other branches.
|
||||
|
||||
### Cache Node.js dependencies
|
||||
|
||||
If your project uses [npm](https://www.npmjs.com/) to install Node.js
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ There are two places defined variables can be used. On the:
|
|||
| [`artifacts:name`](../yaml/index.md#artifactsname) | yes | Runner | The variable expansion is made by GitLab Runner's shell environment. |
|
||||
| [`before_script`](../yaml/index.md#before_script) | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
|
||||
| [`cache:key`](../yaml/index.md#cachekey) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |
|
||||
| [`cache:policy`](../yaml/index.md#cachepolicy) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |
|
||||
| [`environment:name`](../yaml/index.md#environmentname) | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support the following:<br/><br/>- `CI_ENVIRONMENT_*` variables.<br/>- [Persisted variables](#persisted-variables). |
|
||||
| [`environment:url`](../yaml/index.md#environmenturl) | yes | GitLab | The variable expansion is made by the [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism) in GitLab.<br/><br/>Supported are all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules).<br/><br/>Not supported are variables defined in the GitLab Runner `config.toml` and variables created in the job's `script`. |
|
||||
| [`environment:auto_stop_in`](../yaml/index.md#environmentauto_stop_in)| yes | GitLab | The variable expansion is made by the [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism) in GitLab.<br/><br/> The value of the variable being substituted should be a period of time in a human readable natural language form. See [possible inputs](../yaml/index.md#environmentauto_stop_in) for more information.|
|
||||
|
|
|
|||
|
|
@ -1453,6 +1453,7 @@ Must be used with `cache: paths`, or nothing is cached.
|
|||
- `pull`
|
||||
- `push`
|
||||
- `pull-push` (default)
|
||||
- [CI/CD variables](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
|
||||
|
||||
**Example of `cache:policy`**:
|
||||
|
||||
|
|
@ -1480,6 +1481,10 @@ faster-test-job:
|
|||
- echo "Running tests..."
|
||||
```
|
||||
|
||||
**Related topics**:
|
||||
|
||||
- You can [use a variable to control a job's cache policy](../caching/index.md#use-a-variable-to-control-a-jobs-cache-policy).
|
||||
|
||||
#### `cache:fallback_keys`
|
||||
|
||||
Use `cache:fallback_keys` to specify a list of keys to try to restore cache from
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ Because of the many registration paths and multiple verification stages, identit
|
|||
|
||||
Before you enable these features, ensure [hard email confirmation](../security/user_email_confirmation.md) is enabled and [Arkose](../integration/arkose.md#configuration) is configured properly.
|
||||
|
||||
|
||||
| Feature flag name | Description |
|
||||
|---------|-------------|
|
||||
| `identity_verification` | Turns on email verification for all registration paths |
|
||||
|
|
|
|||
|
|
@ -300,6 +300,40 @@ All UI strings should be prepared for translation by following our [internationa
|
|||
|
||||
The strings should use the integration name as [namespace](../i18n/externalization.md#namespaces), for example, `s_('FooBarIntegration|My string')`.
|
||||
|
||||
## Deprecate and remove an integration
|
||||
|
||||
To remove an integration, you must first deprecate the integration. For more information,
|
||||
see the [feature deprecation guidelines](../../development/deprecation_guidelines/index.md).
|
||||
|
||||
### Deprecate an integration
|
||||
|
||||
You must announce any deprecation [no later than the third milestone preceding intended removal](../../development/deprecation_guidelines/index.md#when-can-a-feature-be-deprecated).
|
||||
To deprecate an integration:
|
||||
|
||||
- [Add a deprecation entry](../../development/deprecation_guidelines/index.md#update-the-deprecations-and-removals-documentation-pages).
|
||||
- [Mark the integration documentation as deprecated](../../development/documentation/versions.md#deprecate-a-page-or-topic).
|
||||
- Optional. To prevent any new project-level records from
|
||||
being created, add the integration to `Project#disabled_integrations` (see [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114835)).
|
||||
|
||||
### Remove an integration
|
||||
|
||||
To safely remove an integration, you must stage the removal across two milestones.
|
||||
|
||||
In the major milestone of intended removal (M.0), disable the integration and delete the records from the database:
|
||||
|
||||
- Remove the integration from `Integration::INTEGRATION_NAMES`.
|
||||
- Delete the integration model's `#execute` and `#test` methods (if defined), but keep the model.
|
||||
- Add a post-migration to delete the integration records from PostgreSQL (see [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114721)).
|
||||
- [Add a removal entry](../../development/deprecation_guidelines/index.md#update-the-deprecations-and-removals-documentation-pages).
|
||||
- [Mark the integration documentation as removed](../../development/documentation/versions.md#remove-a-page).
|
||||
- [Update the integration API documentation](../../api/integrations.md).
|
||||
|
||||
In the next minor release (M.1):
|
||||
|
||||
- Remove the integration's model and any remaining code.
|
||||
- Close any issues, merge requests, and epics that have the integration's label (`~Integration::<name>`).
|
||||
- Delete the integration's label (`~Integration::<name>`) from `gitlab-org`.
|
||||
|
||||
## Ongoing migrations and refactorings
|
||||
|
||||
Developers should be aware that the Integrations team is in the process of
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
include ::Gitlab::Config::Entry::Attributable
|
||||
|
||||
ALLOWED_KEYS = %i[key untracked paths when policy unprotect fallback_keys].freeze
|
||||
ALLOWED_POLICY = %w[pull-push push pull].freeze
|
||||
ALLOWED_POLICY = /pull-push|push|pull|\$\w{1,255}*/
|
||||
DEFAULT_POLICY = 'pull-push'
|
||||
ALLOWED_WHEN = %w[on_success on_failure always].freeze
|
||||
DEFAULT_WHEN = 'on_success'
|
||||
|
|
@ -18,9 +18,9 @@ module Gitlab
|
|||
|
||||
validations do
|
||||
validates :config, type: Hash, allowed_keys: ALLOWED_KEYS
|
||||
validates :policy, type: String, allow_blank: true, inclusion: {
|
||||
in: ALLOWED_POLICY,
|
||||
message: "should be one of: #{ALLOWED_POLICY.join(', ')}"
|
||||
validates :policy, type: String, allow_blank: true, format: {
|
||||
with: ALLOWED_POLICY,
|
||||
message: "should be a variable or one of: pull-push, push, pull"
|
||||
}
|
||||
|
||||
with_options allow_nil: true do
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ code_quality:
|
|||
variables:
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
CODE_QUALITY_IMAGE_TAG: "0.94.0"
|
||||
CODE_QUALITY_IMAGE_TAG: "0.96.0"
|
||||
CODE_QUALITY_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/gitlab-org/ci-cd/codequality:$CODE_QUALITY_IMAGE_TAG"
|
||||
needs: []
|
||||
script:
|
||||
|
|
|
|||
|
|
@ -29,6 +29,59 @@ RSpec.describe Projects::MergeRequests::DiffsController, feature_category: :code
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'diff tracking' do
|
||||
it 'tracks mr_diffs event' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to receive(:track_mr_diffs_action)
|
||||
.with(merge_request: merge_request)
|
||||
|
||||
method_call
|
||||
end
|
||||
|
||||
context 'when DNT is enabled' do
|
||||
before do
|
||||
stub_do_not_track('1')
|
||||
end
|
||||
|
||||
it 'does not track any mr_diffs event' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.not_to receive(:track_mr_diffs_action)
|
||||
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.not_to receive(:track_mr_diffs_single_file_action)
|
||||
|
||||
method_call
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has view_diffs_file_by_file set to false' do
|
||||
before do
|
||||
user.update!(view_diffs_file_by_file: false)
|
||||
end
|
||||
|
||||
it 'does not track single_file_diffs events' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.not_to receive(:track_mr_diffs_single_file_action)
|
||||
|
||||
method_call
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has view_diffs_file_by_file set to true' do
|
||||
before do
|
||||
user.update!(view_diffs_file_by_file: true)
|
||||
end
|
||||
|
||||
it 'tracks single_file_diffs events' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to receive(:track_mr_diffs_single_file_action)
|
||||
.with(merge_request: merge_request, user: user)
|
||||
|
||||
method_call
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'forked project with submodules' do
|
||||
render_views
|
||||
|
||||
|
|
@ -327,6 +380,10 @@ RSpec.describe Projects::MergeRequests::DiffsController, feature_category: :code
|
|||
context 'when the merge request exists' do
|
||||
context 'when the user can view the merge request' do
|
||||
context 'when the path exists in the diff' do
|
||||
include_examples 'diff tracking' do
|
||||
let(:method_call) { diff_for_path(old_path: existing_path, new_path: existing_path) }
|
||||
end
|
||||
|
||||
it 'enables diff notes' do
|
||||
diff_for_path(old_path: existing_path, new_path: existing_path)
|
||||
|
||||
|
|
@ -399,6 +456,10 @@ RSpec.describe Projects::MergeRequests::DiffsController, feature_category: :code
|
|||
end
|
||||
|
||||
shared_examples_for 'successful request' do
|
||||
include_examples 'diff tracking' do
|
||||
let(:method_call) { subject }
|
||||
end
|
||||
|
||||
it 'returns success' do
|
||||
subject
|
||||
|
||||
|
|
@ -414,57 +475,6 @@ RSpec.describe Projects::MergeRequests::DiffsController, feature_category: :code
|
|||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'tracks mr_diffs event' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to receive(:track_mr_diffs_action)
|
||||
.with(merge_request: merge_request)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'when DNT is enabled' do
|
||||
before do
|
||||
stub_do_not_track('1')
|
||||
end
|
||||
|
||||
it 'does not track any mr_diffs event' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.not_to receive(:track_mr_diffs_action)
|
||||
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.not_to receive(:track_mr_diffs_single_file_action)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has view_diffs_file_by_file set to false' do
|
||||
before do
|
||||
user.update!(view_diffs_file_by_file: false)
|
||||
end
|
||||
|
||||
it 'does not track single_file_diffs events' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.not_to receive(:track_mr_diffs_single_file_action)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has view_diffs_file_by_file set to true' do
|
||||
before do
|
||||
user.update!(view_diffs_file_by_file: true)
|
||||
end
|
||||
|
||||
it 'tracks single_file_diffs events' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to receive(:track_mr_diffs_single_file_action)
|
||||
.with(merge_request: merge_request, user: user)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def collection_arguments(pagination_data = {})
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
|
|||
|
||||
describe('GroupRunnersApp', () => {
|
||||
let wrapper;
|
||||
const showToast = jest.fn();
|
||||
|
||||
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
|
||||
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
|
||||
|
|
@ -123,6 +124,11 @@ describe('GroupRunnersApp', () => {
|
|||
staleTimeoutSecs,
|
||||
...provide,
|
||||
},
|
||||
mocks: {
|
||||
$toast: {
|
||||
show: showToast,
|
||||
},
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
|
|
@ -250,8 +256,6 @@ describe('GroupRunnersApp', () => {
|
|||
});
|
||||
|
||||
describe('Single runner row', () => {
|
||||
let showToast;
|
||||
|
||||
const { webUrl, editUrl, node } = mockGroupRunnersEdges[0];
|
||||
const { id: graphqlId, shortSha, jobExecutionStatus } = node;
|
||||
const id = getIdFromGraphQLId(graphqlId);
|
||||
|
|
@ -260,7 +264,6 @@ describe('GroupRunnersApp', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await createComponent({ mountFn: mountExtended });
|
||||
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
|
||||
});
|
||||
|
||||
it('Shows job status and links to jobs', () => {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
|
|||
'pull-push' | 'pull-push'
|
||||
'push' | 'push'
|
||||
'pull' | 'pull'
|
||||
'$VARIABLE' | '$VARIABLE'
|
||||
'unknown' | 'unknown' # invalid
|
||||
end
|
||||
|
||||
|
|
@ -145,6 +146,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
|
|||
'pull-push' | true
|
||||
'push' | true
|
||||
'pull' | true
|
||||
'$VARIABLE' | true
|
||||
'unknown' | false
|
||||
end
|
||||
|
||||
|
|
@ -280,7 +282,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
|
|||
let(:config) { { policy: 'unknown' } }
|
||||
|
||||
it 'returns error' do
|
||||
is_expected.to include('cache policy should be one of: pull-push, push, pull')
|
||||
is_expected.to include('cache policy should be a variable or one of: pull-push, push, pull')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -239,391 +239,3 @@ RSpec.shared_examples 'value stream analytics flow metrics deploymentCount examp
|
|||
|
||||
it_behaves_like 'validation on Time arguments'
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'value stream analytics flow metrics leadTime examples' do
|
||||
let_it_be(:milestone) { create(:milestone, group: group) }
|
||||
let_it_be(:label) { create(:group_label, group: group) }
|
||||
|
||||
let_it_be(:author) { create(:user) }
|
||||
let_it_be(:assignee) { create(:user) }
|
||||
|
||||
let_it_be(:issue1) do
|
||||
create(:issue, project: project1, author: author, created_at: 17.days.ago, closed_at: 12.days.ago)
|
||||
end
|
||||
|
||||
let_it_be(:issue2) do
|
||||
create(:issue, project: project2, author: author, created_at: 16.days.ago, closed_at: 13.days.ago)
|
||||
end
|
||||
|
||||
let_it_be(:issue3) do
|
||||
create(:labeled_issue,
|
||||
project: project1,
|
||||
labels: [label],
|
||||
author: author,
|
||||
milestone: milestone,
|
||||
assignees: [assignee],
|
||||
created_at: 14.days.ago,
|
||||
closed_at: 11.days.ago)
|
||||
end
|
||||
|
||||
let_it_be(:issue4) do
|
||||
create(:labeled_issue,
|
||||
project: project2,
|
||||
labels: [label],
|
||||
assignees: [assignee],
|
||||
created_at: 20.days.ago,
|
||||
closed_at: 15.days.ago)
|
||||
end
|
||||
|
||||
before do
|
||||
Analytics::CycleAnalytics::DataLoaderService.new(group: group, model: Issue).execute
|
||||
end
|
||||
|
||||
let(:query) do
|
||||
<<~QUERY
|
||||
query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) {
|
||||
#{context}(fullPath: $path) {
|
||||
flowMetrics {
|
||||
leadTime(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) {
|
||||
value
|
||||
unit
|
||||
identifier
|
||||
title
|
||||
links {
|
||||
label
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
let(:variables) do
|
||||
{
|
||||
path: full_path,
|
||||
from: 21.days.ago.iso8601,
|
||||
to: 10.days.ago.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
subject(:result) do
|
||||
post_graphql(query, current_user: current_user, variables: variables)
|
||||
|
||||
graphql_data.dig(context.to_s, 'flowMetrics', 'leadTime')
|
||||
end
|
||||
|
||||
it 'returns the correct value' do
|
||||
expect(result).to match(a_hash_including({
|
||||
'identifier' => 'lead_time',
|
||||
'unit' => n_('day', 'days', 4),
|
||||
'value' => 4,
|
||||
'title' => _('Lead Time'),
|
||||
'links' => [
|
||||
{ 'label' => s_('ValueStreamAnalytics|Dashboard'), 'url' => match(/issues_analytics/) },
|
||||
{ 'label' => s_('ValueStreamAnalytics|Go to docs'), 'url' => match(/definitions/) }
|
||||
]
|
||||
}))
|
||||
end
|
||||
|
||||
context 'when the user is not authorized' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(result).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when outside of the date range' do
|
||||
let(:variables) do
|
||||
{
|
||||
path: full_path,
|
||||
from: 30.days.ago.iso8601,
|
||||
to: 25.days.ago.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns 0 count' do
|
||||
expect(result).to match(a_hash_including({ 'value' => nil }))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with all filters' do
|
||||
let(:variables) do
|
||||
{
|
||||
path: full_path,
|
||||
assigneeUsernames: [assignee.username],
|
||||
labelNames: [label.title],
|
||||
authorUsername: author.username,
|
||||
milestoneTitle: milestone.title,
|
||||
from: 20.days.ago.iso8601,
|
||||
to: 10.days.ago.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns filtered count' do
|
||||
expect(result).to match(a_hash_including({ 'value' => 3 }))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'value stream analytics flow metrics cycleTime examples' do
|
||||
let_it_be(:milestone) { create(:milestone, group: group) }
|
||||
let_it_be(:label) { create(:group_label, group: group) }
|
||||
|
||||
let_it_be(:author) { create(:user) }
|
||||
let_it_be(:assignee) { create(:user) }
|
||||
|
||||
let_it_be(:issue1) do
|
||||
create(:issue, project: project1, author: author, closed_at: 12.days.ago).tap do |issue|
|
||||
issue.metrics.update!(first_mentioned_in_commit_at: 17.days.ago)
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:issue2) do
|
||||
create(:issue, project: project2, author: author, closed_at: 13.days.ago).tap do |issue|
|
||||
issue.metrics.update!(first_mentioned_in_commit_at: 16.days.ago)
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:issue3) do
|
||||
create(:labeled_issue,
|
||||
project: project1,
|
||||
labels: [label],
|
||||
author: author,
|
||||
milestone: milestone,
|
||||
assignees: [assignee],
|
||||
closed_at: 11.days.ago).tap do |issue|
|
||||
issue.metrics.update!(first_mentioned_in_commit_at: 14.days.ago)
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:issue4) do
|
||||
create(:labeled_issue,
|
||||
project: project2,
|
||||
labels: [label],
|
||||
assignees: [assignee],
|
||||
closed_at: 15.days.ago).tap do |issue|
|
||||
issue.metrics.update!(first_mentioned_in_commit_at: 20.days.ago)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
Analytics::CycleAnalytics::DataLoaderService.new(group: group, model: Issue).execute
|
||||
end
|
||||
|
||||
let(:query) do
|
||||
<<~QUERY
|
||||
query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) {
|
||||
#{context}(fullPath: $path) {
|
||||
flowMetrics {
|
||||
cycleTime(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) {
|
||||
value
|
||||
unit
|
||||
identifier
|
||||
title
|
||||
links {
|
||||
label
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
let(:variables) do
|
||||
{
|
||||
path: full_path,
|
||||
from: 21.days.ago.iso8601,
|
||||
to: 10.days.ago.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
subject(:result) do
|
||||
post_graphql(query, current_user: current_user, variables: variables)
|
||||
|
||||
graphql_data.dig(context.to_s, 'flowMetrics', 'cycleTime')
|
||||
end
|
||||
|
||||
it 'returns the correct value' do
|
||||
expect(result).to eq({
|
||||
'identifier' => 'cycle_time',
|
||||
'unit' => n_('day', 'days', 4),
|
||||
'value' => 4,
|
||||
'title' => _('Cycle Time'),
|
||||
'links' => []
|
||||
})
|
||||
end
|
||||
|
||||
context 'when the user is not authorized' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(result).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when outside of the date range' do
|
||||
let(:variables) do
|
||||
{
|
||||
path: full_path,
|
||||
from: 30.days.ago.iso8601,
|
||||
to: 25.days.ago.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns 0 count' do
|
||||
expect(result).to match(a_hash_including({ 'value' => nil }))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with all filters' do
|
||||
let(:variables) do
|
||||
{
|
||||
path: full_path,
|
||||
assigneeUsernames: [assignee.username],
|
||||
labelNames: [label.title],
|
||||
authorUsername: author.username,
|
||||
milestoneTitle: milestone.title,
|
||||
from: 20.days.ago.iso8601,
|
||||
to: 10.days.ago.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns filtered count' do
|
||||
expect(result).to match(a_hash_including({ 'value' => 3 }))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'value stream analytics flow metrics issuesCompleted examples' do
|
||||
let_it_be(:milestone) { create(:milestone, group: group) }
|
||||
let_it_be(:label) { create(:group_label, group: group) }
|
||||
|
||||
let_it_be(:author) { create(:user) }
|
||||
let_it_be(:assignee) { create(:user) }
|
||||
|
||||
# we don't care about opened date, only closed date.
|
||||
let_it_be(:issue1) do
|
||||
create(:issue, project: project1, author: author, created_at: 17.days.ago, closed_at: 12.days.ago)
|
||||
end
|
||||
|
||||
let_it_be(:issue2) do
|
||||
create(:issue, project: project2, author: author, created_at: 16.days.ago, closed_at: 13.days.ago)
|
||||
end
|
||||
|
||||
let_it_be(:issue3) do
|
||||
create(:labeled_issue,
|
||||
project: project1,
|
||||
labels: [label],
|
||||
author: author,
|
||||
milestone: milestone,
|
||||
assignees: [assignee],
|
||||
created_at: 14.days.ago,
|
||||
closed_at: 11.days.ago)
|
||||
end
|
||||
|
||||
let_it_be(:issue4) do
|
||||
create(:labeled_issue,
|
||||
project: project2,
|
||||
labels: [label],
|
||||
assignees: [assignee],
|
||||
created_at: 20.days.ago,
|
||||
closed_at: 15.days.ago)
|
||||
end
|
||||
|
||||
before do
|
||||
Analytics::CycleAnalytics::DataLoaderService.new(group: group, model: Issue).execute
|
||||
end
|
||||
|
||||
let(:query) do
|
||||
<<~QUERY
|
||||
query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) {
|
||||
#{context}(fullPath: $path) {
|
||||
flowMetrics {
|
||||
issuesCompletedCount(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) {
|
||||
value
|
||||
unit
|
||||
identifier
|
||||
title
|
||||
links {
|
||||
label
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
let(:variables) do
|
||||
{
|
||||
path: full_path,
|
||||
from: 21.days.ago.iso8601,
|
||||
to: 10.days.ago.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
subject(:result) do
|
||||
post_graphql(query, current_user: current_user, variables: variables)
|
||||
|
||||
graphql_data.dig(context.to_s, 'flowMetrics', 'issuesCompletedCount')
|
||||
end
|
||||
|
||||
it 'returns the correct value' do
|
||||
expect(result).to match(a_hash_including({
|
||||
'identifier' => 'issues_completed',
|
||||
'unit' => n_('issue', 'issues', 4),
|
||||
'value' => 4,
|
||||
'title' => _('Issues Completed'),
|
||||
'links' => [
|
||||
{ 'label' => s_('ValueStreamAnalytics|Dashboard'), 'url' => match(/issues_analytics/) },
|
||||
{ 'label' => s_('ValueStreamAnalytics|Go to docs'), 'url' => match(/definitions/) }
|
||||
]
|
||||
}))
|
||||
end
|
||||
|
||||
context 'when the user is not authorized' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(result).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when outside of the date range' do
|
||||
let(:variables) do
|
||||
{
|
||||
path: full_path,
|
||||
from: 30.days.ago.iso8601,
|
||||
to: 25.days.ago.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns 0 count' do
|
||||
expect(result).to match(a_hash_including({ 'value' => 0.0 }))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with all filters' do
|
||||
let(:variables) do
|
||||
{
|
||||
path: full_path,
|
||||
assigneeUsernames: [assignee.username],
|
||||
labelNames: [label.title],
|
||||
authorUsername: author.username,
|
||||
milestoneTitle: milestone.title,
|
||||
from: 20.days.ago.iso8601,
|
||||
to: 10.days.ago.iso8601
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns filtered count' do
|
||||
expect(result).to match(a_hash_including({ 'value' => 1.0 }))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue