Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0e31e4396d
commit
01625f2465
|
|
@ -82,7 +82,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
|
|||
GitLab is a Ruby on Rails application that runs on the following software:
|
||||
|
||||
- Ubuntu/Debian/CentOS/RHEL/OpenSUSE
|
||||
- Ruby (MRI) 3.0.5
|
||||
- Ruby (MRI) 3.1.4
|
||||
- Git 2.33+
|
||||
- Redis 6.0+
|
||||
- PostgreSQL 12+
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
getDateInPast,
|
||||
getCurrentUtcDate,
|
||||
nWeeksBefore,
|
||||
nYearsBefore,
|
||||
} from '~/lib/utils/datetime_utility';
|
||||
import { s__, __, sprintf, n__ } from '~/locale';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
|
|
@ -251,3 +252,43 @@ export const METRICS_POPOVER_CONTENT = {
|
|||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const USAGE_OVERVIEW_NO_DATA_ERROR = s__(
|
||||
'ValueStreamAnalytics|Failed to load usage overview data',
|
||||
);
|
||||
|
||||
export const USAGE_OVERVIEW_DEFAULT_DATE_RANGE = {
|
||||
endDate: TODAY,
|
||||
startDate: nYearsBefore(TODAY, 1),
|
||||
};
|
||||
|
||||
export const USAGE_OVERVIEW_IDENTIFIER_GROUPS = 'groups';
|
||||
export const USAGE_OVERVIEW_IDENTIFIER_PROJECTS = 'projects';
|
||||
export const USAGE_OVERVIEW_IDENTIFIER_ISSUES = 'issues';
|
||||
export const USAGE_OVERVIEW_IDENTIFIER_MERGE_REQUESTS = 'merge_requests';
|
||||
export const USAGE_OVERVIEW_IDENTIFIER_PIPELINES = 'pipelines';
|
||||
|
||||
// Defines the constants used for querying the API as well as the order they appear
|
||||
export const USAGE_OVERVIEW_METADATA = {
|
||||
[USAGE_OVERVIEW_IDENTIFIER_GROUPS]: { options: { title: __('Groups'), titleIcon: 'group' } },
|
||||
[USAGE_OVERVIEW_IDENTIFIER_PROJECTS]: {
|
||||
options: { title: __('Projects'), titleIcon: 'project' },
|
||||
},
|
||||
[USAGE_OVERVIEW_IDENTIFIER_ISSUES]: {
|
||||
options: { title: __('Issues'), titleIcon: 'issues' },
|
||||
},
|
||||
[USAGE_OVERVIEW_IDENTIFIER_MERGE_REQUESTS]: {
|
||||
options: { title: __('Merge requests'), titleIcon: 'merge-request' },
|
||||
},
|
||||
[USAGE_OVERVIEW_IDENTIFIER_PIPELINES]: {
|
||||
options: { title: __('Pipelines'), titleIcon: 'pipeline' },
|
||||
},
|
||||
};
|
||||
|
||||
export const USAGE_OVERVIEW_QUERY_INCLUDE_KEYS = {
|
||||
[USAGE_OVERVIEW_IDENTIFIER_GROUPS]: 'includeGroups',
|
||||
[USAGE_OVERVIEW_IDENTIFIER_PROJECTS]: 'includeProjects',
|
||||
[USAGE_OVERVIEW_IDENTIFIER_ISSUES]: 'includeIssues',
|
||||
[USAGE_OVERVIEW_IDENTIFIER_MERGE_REQUESTS]: 'includeMergeRequests',
|
||||
[USAGE_OVERVIEW_IDENTIFIER_PIPELINES]: 'includePipelines',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -412,6 +412,15 @@ export const nYearsAfter = (date, numberOfYears) => {
|
|||
return clone;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the date `n` years before the date provided.
|
||||
*
|
||||
* @param {Date} date the initial date
|
||||
* @param {Number} numberOfYears number of years before
|
||||
* @return {Date} A `Date` object `n` years before the provided `Date`
|
||||
*/
|
||||
export const nYearsBefore = (date, numberOfYears) => nYearsAfter(date, -numberOfYears);
|
||||
|
||||
/**
|
||||
* Returns the date after the date provided
|
||||
*
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: ['isGroup'],
|
||||
props: {
|
||||
fullPath: {
|
||||
type: String,
|
||||
|
|
@ -152,6 +153,7 @@ export default {
|
|||
note: this.note,
|
||||
name,
|
||||
fullPath: this.fullPath,
|
||||
isGroup: this.isGroup,
|
||||
workItemIid: this.workItemIid,
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export default {
|
|||
components: {
|
||||
AwardsList,
|
||||
},
|
||||
inject: ['isGroup'],
|
||||
props: {
|
||||
fullPath: {
|
||||
type: String,
|
||||
|
|
@ -73,6 +74,7 @@ export default {
|
|||
note: this.note,
|
||||
name,
|
||||
fullPath: this.fullPath,
|
||||
isGroup: this.isGroup,
|
||||
workItemIid: this.workItemIid,
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import workItemNoteCreatedSubscription from '~/work_items/graphql/notes/work_ite
|
|||
import workItemNoteUpdatedSubscription from '~/work_items/graphql/notes/work_item_note_updated.subscription.graphql';
|
||||
import workItemNoteDeletedSubscription from '~/work_items/graphql/notes/work_item_note_deleted.subscription.graphql';
|
||||
import deleteNoteMutation from '../graphql/notes/delete_work_item_notes.mutation.graphql';
|
||||
import groupWorkItemNotesByIidQuery from '../graphql/notes/group_work_item_notes_by_iid.query.graphql';
|
||||
import workItemNotesByIidQuery from '../graphql/notes/work_item_notes_by_iid.query.graphql';
|
||||
import WorkItemAddNote from './notes/work_item_add_note.vue';
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ export default {
|
|||
WorkItemNotesActivityHeader,
|
||||
WorkItemHistoryOnlyFilterNote,
|
||||
},
|
||||
inject: ['isGroup'],
|
||||
props: {
|
||||
fullPath: {
|
||||
type: String,
|
||||
|
|
@ -169,7 +171,9 @@ export default {
|
|||
},
|
||||
apollo: {
|
||||
workItemNotes: {
|
||||
query: workItemNotesByIidQuery,
|
||||
query() {
|
||||
return this.isGroup ? groupWorkItemNotesByIidQuery : workItemNotesByIidQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/notes/work_item_note.fragment.graphql"
|
||||
|
||||
query groupWorkItemNotesByIid($fullPath: ID!, $iid: String, $after: String, $pageSize: Int) {
|
||||
workspace: group(fullPath: $fullPath) {
|
||||
id
|
||||
workItems(iid: $iid) {
|
||||
nodes {
|
||||
id
|
||||
iid
|
||||
widgets {
|
||||
... on WorkItemWidgetNotes {
|
||||
type
|
||||
discussions(first: $pageSize, after: $after, filter: ALL_NOTES) {
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
nodes {
|
||||
id
|
||||
notes {
|
||||
nodes {
|
||||
...WorkItemNote
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import {
|
|||
updateCacheAfterAddingAwardEmojiToNote,
|
||||
updateCacheAfterRemovingAwardEmojiFromNote,
|
||||
} from '~/work_items/graphql/cache_utils';
|
||||
import groupWorkItemNotesByIidQuery from '../graphql/notes/group_work_item_notes_by_iid.query.graphql';
|
||||
import workItemNotesByIidQuery from '../graphql/notes/work_item_notes_by_iid.query.graphql';
|
||||
import addAwardEmojiMutation from '../graphql/notes/work_item_note_add_award_emoji.mutation.graphql';
|
||||
import removeAwardEmojiMutation from '../graphql/notes/work_item_note_remove_award_emoji.mutation.graphql';
|
||||
|
|
@ -32,7 +33,7 @@ export function getMutation({ note, name }) {
|
|||
};
|
||||
}
|
||||
|
||||
export function optimisticAwardUpdate({ note, name, fullPath, workItemIid }) {
|
||||
export function optimisticAwardUpdate({ note, name, fullPath, isGroup, workItemIid }) {
|
||||
const { mutation } = getMutation({ note, name });
|
||||
|
||||
const currentUserId = window.gon.current_user_id;
|
||||
|
|
@ -40,7 +41,7 @@ export function optimisticAwardUpdate({ note, name, fullPath, workItemIid }) {
|
|||
return (store) => {
|
||||
store.updateQuery(
|
||||
{
|
||||
query: workItemNotesByIidQuery,
|
||||
query: isGroup ? groupWorkItemNotesByIidQuery : workItemNotesByIidQuery,
|
||||
variables: { fullPath, iid: workItemIid },
|
||||
},
|
||||
(sourceData) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CleanupCiStagesPipelineIdBigint < Gitlab::Database::Migration[2.2]
|
||||
include Gitlab::Database::MigrationHelpers::WraparoundAutovacuum
|
||||
|
||||
disable_ddl_transaction!
|
||||
milestone "16.7"
|
||||
|
||||
TABLE = :ci_stages
|
||||
REFERENCING_TABLE = :ci_pipelines
|
||||
COLUMN = :pipeline_id
|
||||
OLD_COLUMN = :pipeline_id_convert_to_bigint
|
||||
INDEXES = {
|
||||
'index_ci_stages_on_pipeline_id_convert_to_bigint_and_name' => [
|
||||
[:pipeline_id_convert_to_bigint, :name], { unique: true }
|
||||
],
|
||||
'index_ci_stages_on_pipeline_id_convert_to_bigint' => [
|
||||
[:pipeline_id_convert_to_bigint], {}
|
||||
],
|
||||
'index_ci_stages_on_pipeline_id_convert_to_bigint_and_id' => [
|
||||
[:pipeline_id_convert_to_bigint, :id], { where: 'status = ANY (ARRAY[0, 1, 2, 8, 9, 10])' }
|
||||
],
|
||||
'index_ci_stages_on_pipeline_id_convert_to_bigint_and_position' => [
|
||||
[:pipeline_id_convert_to_bigint, :position], {}
|
||||
]
|
||||
}
|
||||
OLD_FK_NAME = :fk_c5ddde695f
|
||||
|
||||
def up
|
||||
return unless can_execute_on?(:ci_pipelines, :ci_stages)
|
||||
|
||||
with_lock_retries(raise_on_exhaustion: true) do
|
||||
lock_tables(REFERENCING_TABLE, TABLE)
|
||||
cleanup_conversion_of_integer_to_bigint(TABLE, [COLUMN])
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
return unless can_execute_on?(:ci_pipelines, :ci_stages)
|
||||
|
||||
restore_conversion_of_integer_to_bigint(TABLE, [COLUMN])
|
||||
|
||||
INDEXES.each do |index_name, (columns, options)|
|
||||
add_concurrent_index(TABLE, columns, name: index_name, **options)
|
||||
end
|
||||
|
||||
add_concurrent_foreign_key(
|
||||
TABLE, REFERENCING_TABLE,
|
||||
column: OLD_COLUMN, name: OLD_FK_NAME,
|
||||
on_delete: :cascade, validate: true, reverse_lock_order: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
7f3abae7002d20e30f9e4a30d580e49c5d72a7728d13ee45a5392fb4396da13b
|
||||
|
|
@ -531,15 +531,6 @@ RETURN NULL;
|
|||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_07bc3c48f407() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW."pipeline_id_convert_to_bigint" := NEW."pipeline_id";
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_10ee1357e825() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
|
@ -14721,7 +14712,6 @@ ALTER SEQUENCE ci_sources_projects_id_seq OWNED BY ci_sources_projects.id;
|
|||
|
||||
CREATE TABLE ci_stages (
|
||||
project_id integer,
|
||||
pipeline_id_convert_to_bigint integer,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone,
|
||||
name character varying,
|
||||
|
|
@ -32344,14 +32334,6 @@ CREATE UNIQUE INDEX index_ci_stages_on_pipeline_id_and_name ON ci_stages USING b
|
|||
|
||||
CREATE INDEX index_ci_stages_on_pipeline_id_and_position ON ci_stages USING btree (pipeline_id, "position");
|
||||
|
||||
CREATE INDEX index_ci_stages_on_pipeline_id_convert_to_bigint ON ci_stages USING btree (pipeline_id_convert_to_bigint);
|
||||
|
||||
CREATE INDEX index_ci_stages_on_pipeline_id_convert_to_bigint_and_id ON ci_stages USING btree (pipeline_id_convert_to_bigint, id) WHERE (status = ANY (ARRAY[0, 1, 2, 8, 9, 10]));
|
||||
|
||||
CREATE UNIQUE INDEX index_ci_stages_on_pipeline_id_convert_to_bigint_and_name ON ci_stages USING btree (pipeline_id_convert_to_bigint, name);
|
||||
|
||||
CREATE INDEX index_ci_stages_on_pipeline_id_convert_to_bigint_and_position ON ci_stages USING btree (pipeline_id_convert_to_bigint, "position");
|
||||
|
||||
CREATE INDEX index_ci_stages_on_project_id ON ci_stages USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_ci_subscriptions_projects_author_id ON ci_subscriptions_projects USING btree (author_id);
|
||||
|
|
@ -37036,8 +37018,6 @@ CREATE TRIGGER push_rules_loose_fk_trigger AFTER DELETE ON push_rules REFERENCIN
|
|||
|
||||
CREATE TRIGGER tags_loose_fk_trigger AFTER DELETE ON tags REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
|
||||
|
||||
CREATE TRIGGER trigger_07bc3c48f407 BEFORE INSERT OR UPDATE ON ci_stages FOR EACH ROW EXECUTE FUNCTION trigger_07bc3c48f407();
|
||||
|
||||
CREATE TRIGGER trigger_10ee1357e825 BEFORE INSERT OR UPDATE ON p_ci_builds FOR EACH ROW EXECUTE FUNCTION trigger_10ee1357e825();
|
||||
|
||||
CREATE TRIGGER trigger_b2d852e1e2cb BEFORE INSERT OR UPDATE ON ci_pipelines FOR EACH ROW EXECUTE FUNCTION trigger_b2d852e1e2cb();
|
||||
|
|
@ -37953,9 +37933,6 @@ ALTER TABLE ONLY timelogs
|
|||
ALTER TABLE ONLY geo_event_log
|
||||
ADD CONSTRAINT fk_c4b1c1f66e FOREIGN KEY (repository_deleted_event_id) REFERENCES geo_repository_deleted_events(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_stages
|
||||
ADD CONSTRAINT fk_c5ddde695f FOREIGN KEY (pipeline_id_convert_to_bigint) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY issues
|
||||
ADD CONSTRAINT fk_c63cbf6c25 FOREIGN KEY (closed_by_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ Audit events can be viewed at the group, project, instance, and sign-in level. E
|
|||
To view a group's audit events:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. On the left sidebar, select **Secure > Audit events**.
|
||||
1. Select **Secure > Audit events**.
|
||||
1. Filter the audit events by the member of the project (user) who performed the action and date range.
|
||||
|
||||
Group audit events can also be accessed using the [Group Audit Events API](../api/audit_events.md#group-audit-events). Group audit event queries are limited to a maximum of 30 days.
|
||||
|
|
@ -45,7 +45,7 @@ Group audit events can also be accessed using the [Group Audit Events API](../ap
|
|||
### Project audit events
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. On the left sidebar, select **Secure > Audit events**.
|
||||
1. Select **Secure > Audit events**.
|
||||
1. Filter the audit events by the member of the project (user) who performed the action and date range.
|
||||
|
||||
Project audit events can also be accessed using the [Project Audit Events API](../api/audit_events.md#project-audit-events). Project audit event queries are limited to a maximum of 30 days.
|
||||
|
|
@ -56,7 +56,7 @@ You can view audit events from user actions across an entire GitLab instance.
|
|||
To view instance audit events:
|
||||
|
||||
1. On the left sidebar, at the bottom, select **Admin Area**.
|
||||
1. On the left sidebar, select **Monitoring > Audit Events**.
|
||||
1. Select **Monitoring > Audit Events**.
|
||||
1. Filter by the following:
|
||||
- Member of the project (user) who performed the action
|
||||
- Group
|
||||
|
|
@ -82,7 +82,7 @@ You can export the current view (including filters) of your instance audit event
|
|||
CSV(comma-separated values) file. To export the instance audit events to CSV:
|
||||
|
||||
1. On the left sidebar, at the bottom, select **Admin Area**.
|
||||
1. On the left sidebar, select **Monitoring > Audit Events**.
|
||||
1. Select **Monitoring > Audit Events**.
|
||||
1. Select the available search filters.
|
||||
1. Select **Export as CSV**.
|
||||
|
||||
|
|
|
|||
|
|
@ -1010,8 +1010,11 @@ To open the Admin Area:
|
|||
|
||||
```markdown
|
||||
1. On the left sidebar, at the bottom, select **Admin Area**.
|
||||
1. Select **Settings > CI/CD**.
|
||||
```
|
||||
|
||||
You do not need to repeat `On the left sidebar` in your second step.
|
||||
|
||||
To open the **Your work** menu item:
|
||||
|
||||
```markdown
|
||||
|
|
|
|||
|
|
@ -380,6 +380,18 @@ Use **compute minutes** instead of these (or similar) terms:
|
|||
|
||||
For more information, see [epic 2150](https://gitlab.com/groups/gitlab-com/-/epics/2150).
|
||||
|
||||
## configuration
|
||||
|
||||
When you update a collection of settings, call it a **configuration**.
|
||||
|
||||
## configure
|
||||
|
||||
Use **configure** after a feature or product has been [set up](#setup-set-up).
|
||||
For example:
|
||||
|
||||
1. Set up your installation.
|
||||
1. Configure your installation.
|
||||
|
||||
## confirmation dialog
|
||||
|
||||
Use **confirmation dialog** to describe the dialog that asks you to confirm an action. For example:
|
||||
|
|
@ -1335,6 +1347,10 @@ see the [Microsoft Style Guide](https://learn.microsoft.com/en-us/style-guide/a-
|
|||
Use **Premium**, in uppercase, for the subscription tier. When you refer to **Premium**
|
||||
in the context of other subscription tiers, follow [the subscription tier](#subscription-tier) guidance.
|
||||
|
||||
## preferences
|
||||
|
||||
Use **preferences** to describe user-specific, system-level settings like theme and layout.
|
||||
|
||||
## prerequisites
|
||||
|
||||
Use **prerequisites** when documenting the tasks that must be completed or the conditions that must be met before a user can complete a task. Do not use **requirements**.
|
||||
|
|
@ -1556,6 +1572,17 @@ Use **setup** as a noun, and **set up** as a verb. For example:
|
|||
- Your remote office setup is amazing.
|
||||
- To set up your remote office correctly, consider the ergonomics of your work area.
|
||||
|
||||
Do not confuse **set up** with [**configure**](#configure).
|
||||
**Set up** implies that it's the first time you've done something. For example:
|
||||
|
||||
1. Set up your installation.
|
||||
1. Configure your installation.
|
||||
|
||||
## settings
|
||||
|
||||
A **setting** changes the default behavior of the product. A **setting** consists of a key/value pair,
|
||||
typically represented by a label with one or more options.
|
||||
|
||||
## sign in, sign-in
|
||||
|
||||
To describe the action of signing in, use:
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ To leave feedback about Product Analytics bugs or functionality:
|
|||
|
||||
- Comment on [issue 391970](https://gitlab.com/gitlab-org/gitlab/-/issues/391970).
|
||||
- Create an issue with the `group::product analytics` label.
|
||||
- [Schedule a call](https://calendly.com/jheimbuck/30-minute-call) with the team.
|
||||
|
||||
## How product analytics works
|
||||
|
||||
|
|
|
|||
|
|
@ -53029,6 +53029,9 @@ msgstr ""
|
|||
msgid "ValueStreamAnalytics|Edit Value Stream: %{name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|Failed to load usage overview data"
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|Go to docs"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -800,6 +800,21 @@ describe('date addition/subtraction methods', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('nYearsBefore', () => {
|
||||
it.each`
|
||||
date | numberOfYears | expected
|
||||
${'2020-07-06'} | ${4} | ${'2016-07-06'}
|
||||
${'2020-07-06'} | ${1} | ${'2019-07-06'}
|
||||
`(
|
||||
'returns $expected for "$numberOfYears year(s) before $date"',
|
||||
({ date, numberOfYears, expected }) => {
|
||||
expect(datetimeUtility.nYearsBefore(new Date(date), numberOfYears)).toEqual(
|
||||
new Date(expected),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('nMonthsBefore', () => {
|
||||
// The previous month (February) has 28 days
|
||||
const march2019 = '2019-03-15T00:00:00.000Z';
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ describe('Work Item Note Actions', () => {
|
|||
projectName,
|
||||
},
|
||||
provide: {
|
||||
isGroup: false,
|
||||
glFeatures: {
|
||||
workItemsMvc2: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import AwardsList from '~/vue_shared/components/awards_list.vue';
|
|||
import WorkItemNoteAwardsList from '~/work_items/components/notes/work_item_note_awards_list.vue';
|
||||
import addAwardEmojiMutation from '~/work_items/graphql/notes/work_item_note_add_award_emoji.mutation.graphql';
|
||||
import removeAwardEmojiMutation from '~/work_items/graphql/notes/work_item_note_remove_award_emoji.mutation.graphql';
|
||||
import groupWorkItemNotesByIidQuery from '~/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql';
|
||||
import workItemNotesByIidQuery from '~/work_items/graphql/notes/work_item_notes_by_iid.query.graphql';
|
||||
import {
|
||||
mockWorkItemNotesResponseWithComments,
|
||||
|
|
@ -45,6 +46,7 @@ describe('Work Item Note Awards List', () => {
|
|||
const findAwardsList = () => wrapper.findComponent(AwardsList);
|
||||
|
||||
const createComponent = ({
|
||||
isGroup = false,
|
||||
note = firstNote,
|
||||
addAwardEmojiMutationHandler = addAwardEmojiMutationSuccessHandler,
|
||||
removeAwardEmojiMutationHandler = removeAwardEmojiMutationSuccessHandler,
|
||||
|
|
@ -55,12 +57,15 @@ describe('Work Item Note Awards List', () => {
|
|||
]);
|
||||
|
||||
apolloProvider.clients.defaultClient.writeQuery({
|
||||
query: workItemNotesByIidQuery,
|
||||
query: isGroup ? groupWorkItemNotesByIidQuery : workItemNotesByIidQuery,
|
||||
variables: { fullPath, iid: workItemIid },
|
||||
...mockWorkItemNotesResponseWithComments,
|
||||
});
|
||||
|
||||
wrapper = shallowMount(WorkItemNoteAwardsList, {
|
||||
provide: {
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
fullPath,
|
||||
workItemIid,
|
||||
|
|
@ -89,17 +94,20 @@ describe('Work Item Note Awards List', () => {
|
|||
expect(findAwardsList().props('canAwardEmoji')).toBe(hasAwardEmojiPermission);
|
||||
});
|
||||
|
||||
it('adds award if not already awarded', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
it.each([true, false])(
|
||||
'adds award if not already awarded in both group and project contexts',
|
||||
async (isGroup) => {
|
||||
createComponent({ isGroup });
|
||||
await waitForPromises();
|
||||
|
||||
findAwardsList().vm.$emit('award', EMOJI_THUMBSUP);
|
||||
findAwardsList().vm.$emit('award', EMOJI_THUMBSUP);
|
||||
|
||||
expect(addAwardEmojiMutationSuccessHandler).toHaveBeenCalledWith({
|
||||
awardableId: firstNote.id,
|
||||
name: EMOJI_THUMBSUP,
|
||||
});
|
||||
});
|
||||
expect(addAwardEmojiMutationSuccessHandler).toHaveBeenCalledWith({
|
||||
awardableId: firstNote.id,
|
||||
name: EMOJI_THUMBSUP,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('emits error if awarding emoji fails', async () => {
|
||||
createComponent({
|
||||
|
|
@ -114,20 +122,23 @@ describe('Work Item Note Awards List', () => {
|
|||
expect(wrapper.emitted('error')).toEqual([[__('Failed to add emoji. Please try again')]]);
|
||||
});
|
||||
|
||||
it('removes award if already awarded', async () => {
|
||||
const removeAwardEmojiMutationHandler = removeAwardEmojiMutationSuccessHandler;
|
||||
it.each([true, false])(
|
||||
'removes award if already awarded in both group and project contexts',
|
||||
async (isGroup) => {
|
||||
const removeAwardEmojiMutationHandler = removeAwardEmojiMutationSuccessHandler;
|
||||
|
||||
createComponent({ removeAwardEmojiMutationHandler });
|
||||
createComponent({ isGroup, removeAwardEmojiMutationHandler });
|
||||
|
||||
findAwardsList().vm.$emit('award', EMOJI_THUMBSDOWN);
|
||||
findAwardsList().vm.$emit('award', EMOJI_THUMBSDOWN);
|
||||
|
||||
await waitForPromises();
|
||||
await waitForPromises();
|
||||
|
||||
expect(removeAwardEmojiMutationHandler).toHaveBeenCalledWith({
|
||||
awardableId: firstNote.id,
|
||||
name: EMOJI_THUMBSDOWN,
|
||||
});
|
||||
});
|
||||
expect(removeAwardEmojiMutationHandler).toHaveBeenCalledWith({
|
||||
awardableId: firstNote.id,
|
||||
name: EMOJI_THUMBSDOWN,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('restores award if remove fails', async () => {
|
||||
createComponent({
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import WorkItemNotes from '~/work_items/components/work_item_notes.vue';
|
|||
import WorkItemDiscussion from '~/work_items/components/notes/work_item_discussion.vue';
|
||||
import WorkItemAddNote from '~/work_items/components/notes/work_item_add_note.vue';
|
||||
import WorkItemNotesActivityHeader from '~/work_items/components/notes/work_item_notes_activity_header.vue';
|
||||
import groupWorkItemNotesByIidQuery from '~/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql';
|
||||
import workItemNotesByIidQuery from '~/work_items/graphql/notes/work_item_notes_by_iid.query.graphql';
|
||||
import deleteWorkItemNoteMutation from '~/work_items/graphql/notes/delete_work_item_notes.mutation.graphql';
|
||||
import workItemNoteCreatedSubscription from '~/work_items/graphql/notes/work_item_note_created.subscription.graphql';
|
||||
|
|
@ -63,6 +64,9 @@ describe('WorkItemNotes component', () => {
|
|||
const findWorkItemCommentNoteAtIndex = (index) => findAllWorkItemCommentNotes().at(index);
|
||||
const findDeleteNoteModal = () => wrapper.findComponent(GlModal);
|
||||
|
||||
const groupWorkItemNotesQueryHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkItemNotesByIidResponse);
|
||||
const workItemNotesQueryHandler = jest.fn().mockResolvedValue(mockWorkItemNotesByIidResponse);
|
||||
const workItemMoreNotesQueryHandler = jest.fn().mockResolvedValue(mockMoreWorkItemNotesResponse);
|
||||
const workItemNotesWithCommentsQueryHandler = jest
|
||||
|
|
@ -87,17 +91,22 @@ describe('WorkItemNotes component', () => {
|
|||
workItemIid = mockWorkItemIid,
|
||||
defaultWorkItemNotesQueryHandler = workItemNotesQueryHandler,
|
||||
deleteWINoteMutationHandler = deleteWorkItemNoteMutationSuccessHandler,
|
||||
isGroup = false,
|
||||
isModal = false,
|
||||
isWorkItemConfidential = false,
|
||||
} = {}) => {
|
||||
wrapper = shallowMount(WorkItemNotes, {
|
||||
apolloProvider: createMockApollo([
|
||||
[workItemNotesByIidQuery, defaultWorkItemNotesQueryHandler],
|
||||
[groupWorkItemNotesByIidQuery, groupWorkItemNotesQueryHandler],
|
||||
[deleteWorkItemNoteMutation, deleteWINoteMutationHandler],
|
||||
[workItemNoteCreatedSubscription, notesCreateSubscriptionHandler],
|
||||
[workItemNoteUpdatedSubscription, notesUpdateSubscriptionHandler],
|
||||
[workItemNoteDeletedSubscription, notesDeleteSubscriptionHandler],
|
||||
]),
|
||||
provide: {
|
||||
isGroup,
|
||||
},
|
||||
propsData: {
|
||||
fullPath: 'test-path',
|
||||
workItemId,
|
||||
|
|
@ -354,4 +363,22 @@ describe('WorkItemNotes component', () => {
|
|||
|
||||
expect(findWorkItemCommentNoteAtIndex(0).props('isWorkItemConfidential')).toBe(true);
|
||||
});
|
||||
|
||||
describe('when project context', () => {
|
||||
it('calls the project work item query', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
expect(workItemNotesQueryHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when group context', () => {
|
||||
it('calls the group work item query', async () => {
|
||||
createComponent({ isGroup: true });
|
||||
await waitForPromises();
|
||||
|
||||
expect(groupWorkItemNotesQueryHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { getMutation, optimisticAwardUpdate } from '~/work_items/notes/award_uti
|
|||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import mockApollo from 'helpers/mock_apollo_helper';
|
||||
import { __ } from '~/locale';
|
||||
import groupWorkItemNotesByIidQuery from '~/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql';
|
||||
import workItemNotesByIidQuery from '~/work_items/graphql/notes/work_item_notes_by_iid.query.graphql';
|
||||
import addAwardEmojiMutation from '~/work_items/graphql/notes/work_item_note_add_award_emoji.mutation.graphql';
|
||||
import removeAwardEmojiMutation from '~/work_items/graphql/notes/work_item_note_remove_award_emoji.mutation.graphql';
|
||||
|
|
@ -105,5 +106,22 @@ describe('Work item note award utils', () => {
|
|||
|
||||
expect(updatedNote.awardEmoji.nodes).toEqual([]);
|
||||
});
|
||||
|
||||
it.each`
|
||||
description | isGroup | query
|
||||
${'calls project query when in project context'} | ${false} | ${workItemNotesByIidQuery}
|
||||
${'calls group query when in group context'} | ${true} | ${groupWorkItemNotesByIidQuery}
|
||||
`('$description', ({ isGroup, query }) => {
|
||||
const note = firstNote;
|
||||
const { name } = mockAwardEmojiThumbsUp;
|
||||
const cacheSpy = { updateQuery: jest.fn() };
|
||||
|
||||
optimisticAwardUpdate({ note, name, fullPath, isGroup, workItemIid })(cacheSpy);
|
||||
|
||||
expect(cacheSpy.updateQuery).toHaveBeenCalledWith(
|
||||
{ query, variables: { fullPath, iid: workItemIid } },
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,10 +42,6 @@ ci_stages:
|
|||
- index_ci_stages_on_pipeline_id
|
||||
index_ci_stages_on_pipeline_id_and_position:
|
||||
- index_ci_stages_on_pipeline_id
|
||||
index_ci_stages_on_pipeline_id_convert_to_bigint_and_name:
|
||||
- index_ci_stages_on_pipeline_id_convert_to_bigint
|
||||
index_ci_stages_on_pipeline_id_convert_to_bigint_and_position:
|
||||
- index_ci_stages_on_pipeline_id_convert_to_bigint
|
||||
dast_site_tokens:
|
||||
index_dast_site_token_on_project_id_and_url:
|
||||
- index_dast_site_tokens_on_project_id
|
||||
|
|
|
|||
Loading…
Reference in New Issue