Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-08-17 15:10:09 +00:00
parent 4203215d54
commit 073ebdcae8
23 changed files with 288 additions and 697 deletions

View File

@ -339,8 +339,8 @@ Graphql/AuthorizeTypes:
Graphql/JSONType:
Enabled: true
Include:
- 'app/graphql/types/**/*'
- 'ee/app/graphql/types/**/*'
- 'app/graphql/**/*'
- 'ee/app/graphql/**/*'
Exclude:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'

View File

@ -1,11 +1,7 @@
<script>
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { last } from 'lodash';
import { __ } from '~/locale';
import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql';
import getJiraUserMappingMutation from '../queries/get_jira_user_mapping.mutation.graphql';
import initiateJiraImportMutation from '../queries/initiate_jira_import.mutation.graphql';
import { addInProgressImportToStore } from '../utils/cache_update';
import { isInProgress, extractJiraProjectsOptions } from '../utils/jira_import_utils';
import JiraImportForm from './jira_import_form.vue';
import JiraImportProgress from './jira_import_progress.vue';
@ -52,9 +48,7 @@ export default {
},
data() {
return {
isSubmitting: false,
jiraImportDetails: {},
userMappings: [],
errorMessage: '',
showAlert: false,
};
@ -78,70 +72,7 @@ export default {
},
},
},
mounted() {
if (this.isJiraConfigured) {
this.$apollo
.mutate({
mutation: getJiraUserMappingMutation,
variables: {
input: {
projectPath: this.projectPath,
},
},
})
.then(({ data }) => {
if (data.jiraImportUsers.errors.length) {
this.setAlertMessage(data.jiraImportUsers.errors.join('. '));
} else {
this.userMappings = data.jiraImportUsers.jiraUsers;
}
})
.catch(() => this.setAlertMessage(__('There was an error retrieving the Jira users.')));
}
},
methods: {
initiateJiraImport(project) {
this.isSubmitting = true;
this.$apollo
.mutate({
mutation: initiateJiraImportMutation,
variables: {
input: {
jiraProjectKey: project,
projectPath: this.projectPath,
usersMapping: this.userMappings.map(({ gitlabId, jiraAccountId }) => ({
gitlabId,
jiraAccountId,
})),
},
},
update: (store, { data }) =>
addInProgressImportToStore(store, data.jiraImportStart, this.projectPath),
})
.then(({ data }) => {
if (data.jiraImportStart.errors.length) {
this.setAlertMessage(data.jiraImportStart.errors.join('. '));
} else {
this.selectedProject = undefined;
}
})
.catch(() => this.setAlertMessage(__('There was an error importing the Jira project.')))
.finally(() => {
this.isSubmitting = false;
});
},
updateMapping(jiraAccountId, gitlabId, gitlabUsername) {
this.userMappings = this.userMappings.map(userMapping =>
userMapping.jiraAccountId === jiraAccountId
? {
...userMapping,
gitlabId,
gitlabUsername,
}
: userMapping,
);
},
setAlertMessage(message) {
this.errorMessage = message;
this.showAlert = true;
@ -175,14 +106,12 @@ export default {
/>
<jira-import-form
v-else
:is-submitting="isSubmitting"
:issues-path="issuesPath"
:jira-imports="jiraImportDetails.imports"
:jira-projects="jiraImportDetails.projects"
:project-id="projectId"
:user-mappings="userMappings"
@initiateJiraImport="initiateJiraImport"
@updateMapping="updateMapping"
:project-path="projectPath"
@error="setAlertMessage"
/>
</div>
</template>

View File

@ -16,6 +16,10 @@ import {
} from '@gitlab/ui';
import { debounce } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import getJiraUserMappingMutation from '../queries/get_jira_user_mapping.mutation.graphql';
import initiateJiraImportMutation from '../queries/initiate_jira_import.mutation.graphql';
import { addInProgressImportToStore } from '../utils/cache_update';
import {
debounceWait,
dropdownLabel,
@ -47,10 +51,6 @@ export default {
tableConfig,
userMappingMessage,
props: {
isSubmitting: {
type: Boolean,
required: true,
},
issuesPath: {
type: String,
required: true,
@ -67,17 +67,19 @@ export default {
type: String,
required: true,
},
userMappings: {
type: Array,
projectPath: {
type: String,
required: true,
},
},
data() {
return {
isFetching: false,
isSubmitting: false,
searchTerm: '',
selectedProject: undefined,
selectState: null,
userMappings: [],
users: [],
};
},
@ -106,6 +108,24 @@ export default {
}, debounceWait),
},
mounted() {
this.$apollo
.mutate({
mutation: getJiraUserMappingMutation,
variables: {
input: {
projectPath: this.projectPath,
},
},
})
.then(({ data }) => {
if (data.jiraImportUsers.errors.length) {
this.$emit('error', data.jiraImportUsers.errors.join('. '));
} else {
this.userMappings = data.jiraImportUsers.jiraUsers;
}
})
.catch(() => this.$emit('error', __('There was an error retrieving the Jira users.')));
this.searchUsers()
.then(data => {
this.initialUsers = data;
@ -138,13 +158,54 @@ export default {
},
initiateJiraImport(event) {
event.preventDefault();
if (this.selectedProject) {
this.hideValidationError();
this.$emit('initiateJiraImport', this.selectedProject);
this.isSubmitting = true;
this.$apollo
.mutate({
mutation: initiateJiraImportMutation,
variables: {
input: {
jiraProjectKey: this.selectedProject,
projectPath: this.projectPath,
usersMapping: this.userMappings.map(({ gitlabId, jiraAccountId }) => ({
gitlabId,
jiraAccountId,
})),
},
},
update: (store, { data }) =>
addInProgressImportToStore(store, data.jiraImportStart, this.projectPath),
})
.then(({ data }) => {
if (data.jiraImportStart.errors.length) {
this.$emit('error', data.jiraImportStart.errors.join('. '));
} else {
this.selectedProject = undefined;
}
})
.catch(() => this.$emit('error', __('There was an error importing the Jira project.')))
.finally(() => {
this.isSubmitting = false;
});
} else {
this.showValidationError();
}
},
updateMapping(jiraAccountId, gitlabId, gitlabUsername) {
this.userMappings = this.userMappings.map(userMapping =>
userMapping.jiraAccountId === jiraAccountId
? {
...userMapping,
gitlabId,
gitlabUsername,
}
: userMapping,
);
},
hideValidationError() {
this.selectState = null;
},
@ -227,7 +288,7 @@ export default {
v-for="user in users"
v-else
:key="user.id"
@click="$emit('updateMapping', data.item.jiraAccountId, user.id, user.username)"
@click="updateMapping(data.item.jiraAccountId, user.id, user.username)"
>
{{ user.username }} ({{ user.name }})
</gl-new-dropdown-item>

View File

@ -156,6 +156,9 @@ function deferredInitialisation() {
});
loadAwardsHandler();
// Adding a helper class to activate animations only after all is rendered
setTimeout(() => $body.addClass('page-initialised'), 1000);
}
document.addEventListener('DOMContentLoaded', () => {

View File

@ -1,6 +1,5 @@
.content-wrapper {
width: 100%;
transition: padding $sidebar-transition-duration;
.container-fluid {
padding: 0 $gl-padding;
@ -13,6 +12,10 @@
}
}
.page-initialised .content-wrapper {
transition: padding $sidebar-transition-duration;
}
.nav-header-btn {
padding: 10px $gl-sidebar-padding;
color: inherit;

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class AddDeleteOriginalIndexAtToReindexingTasks < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
add_column :elastic_reindexing_tasks, :delete_original_index_at, :datetime_with_timezone
end
end
def down
with_lock_retries do
remove_column :elastic_reindexing_tasks, :delete_original_index_at
end
end
end

View File

@ -0,0 +1 @@
867cea94f966c1ad3d9e02f7fa5b641ceac5a71667426330c2c96d6181164f66

View File

@ -11353,6 +11353,7 @@ CREATE TABLE public.elastic_reindexing_tasks (
elastic_task text,
error_message text,
documents_count_target integer,
delete_original_index_at timestamp with time zone,
CONSTRAINT check_04151aca42 CHECK ((char_length(index_name_from) <= 255)),
CONSTRAINT check_7f64acda8e CHECK ((char_length(error_message) <= 255)),
CONSTRAINT check_85ebff7124 CHECK ((char_length(index_name_to) <= 255)),

View File

@ -37,8 +37,7 @@ recover. See below for more details.
The following guide assumes that:
- You are using Omnibus and therefore you are using PostgreSQL 11 or later
which includes the [`pg_basebackup` tool](https://www.postgresql.org/docs/11/app-pgbasebackup.html) and improved
[Foreign Data Wrapper](https://www.postgresql.org/docs/11/postgres-fdw.html) support.
which includes the [`pg_basebackup` tool](https://www.postgresql.org/docs/11/app-pgbasebackup.html).
- You have a **primary** node already set up (the GitLab server you are
replicating from), running Omnibus' PostgreSQL (or equivalent version), and
you have a new **secondary** server set up with the same versions of the OS,
@ -346,10 +345,10 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
Ensure that the contents of `~gitlab-psql/data/server.crt` on the **primary** node
match the contents of `~gitlab-psql/.postgresql/root.crt` on the **secondary** node.
1. Configure PostgreSQL to enable FDW support:
1. Configure PostgreSQL:
This step is similar to how we configured the **primary** instance.
We need to enable this, to enable FDW support, even if using a single node.
We need to enable this, even if using a single node.
Edit `/etc/gitlab/gitlab.rb` and add the following, replacing the IP
addresses with addresses appropriate to your network configuration:
@ -375,11 +374,6 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
gitlab_rails['db_password'] = '<your_password_here>'
##
## Enable FDW support for the Geo Tracking Database (improves performance)
##
geo_secondary['db_fdw'] = true
```
For external PostgreSQL instances, see [additional instructions](external_database.md).
If you bring a former **primary** node back online to serve as a **secondary** node, then you also need to remove `roles ['geo_primary_role']` or `geo_primary_role['enable'] = true`.
@ -390,15 +384,12 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
gitlab-ctl reconfigure
```
1. Restart PostgreSQL for the IP change to take effect and reconfigure again:
1. Restart PostgreSQL for the IP change to take effect:
```shell
gitlab-ctl restart postgresql
gitlab-ctl reconfigure
```
This last reconfigure will provision the FDW configuration and enable it.
### Step 3. Initiate the replication process
Below we provide a script that connects the database on the **secondary** node to
@ -473,48 +464,6 @@ high-availability configuration with a cluster of nodes supporting a Geo
**primary** node and another cluster of nodes supporting a Geo **secondary** node. For more
information, see [High Availability with Omnibus GitLab](../../postgresql/replication_and_failover.md).
For a Geo **secondary** node to work properly with PgBouncer in front of the database,
it will need a separate read-only user to make [PostgreSQL FDW queries](https://www.postgresql.org/docs/11/postgres-fdw.html)
work:
1. On the **primary** Geo database, enter the PostgreSQL on the console as an
admin user. If you are using an Omnibus-managed database, log onto the **primary**
node that is running the PostgreSQL database (the default Omnibus database name is `gitlabhq_production`):
```shell
sudo \
-u gitlab-psql /opt/gitlab/embedded/bin/psql \
-h /var/opt/gitlab/postgresql gitlabhq_production
```
1. Then create the read-only user:
```sql
-- NOTE: Use the password defined earlier
CREATE USER gitlab_geo_fdw WITH password 'mypassword';
GRANT CONNECT ON DATABASE gitlabhq_production to gitlab_geo_fdw;
GRANT USAGE ON SCHEMA public TO gitlab_geo_fdw;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO gitlab_geo_fdw;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO gitlab_geo_fdw;
-- Tables created by "gitlab" should be made read-only for "gitlab_geo_fdw"
-- automatically.
ALTER DEFAULT PRIVILEGES FOR USER gitlab IN SCHEMA public GRANT SELECT ON TABLES TO gitlab_geo_fdw;
ALTER DEFAULT PRIVILEGES FOR USER gitlab IN SCHEMA public GRANT SELECT ON SEQUENCES TO gitlab_geo_fdw;
```
1. On the **secondary** nodes, change `/etc/gitlab/gitlab.rb`:
```ruby
geo_postgresql['fdw_external_user'] = 'gitlab_geo_fdw'
```
1. Save the file and reconfigure GitLab for the changes to be applied:
```shell
gitlab-ctl reconfigure
```
## Troubleshooting
Read the [troubleshooting document](troubleshooting.md).

View File

@ -183,9 +183,6 @@ to grant additional roles to your tracking database user (by default, this is
- Amazon RDS requires the [`rds_superuser`](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.html#Appendix.PostgreSQL.CommonDBATasks.Roles) role.
- Azure Database for PostgreSQL requires the [`azure_pg_admin`](https://docs.microsoft.com/en-us/azure/postgresql/howto-create-users#how-to-create-additional-admin-users-in-azure-database-for-postgresql) role.
The tracking database requires an [FDW](https://www.postgresql.org/docs/11/postgres-fdw.html)
connection with the **secondary** replica database for improved performance.
If you have an external database ready to be used as the tracking database,
follow the instructions below to use it:
@ -224,7 +221,6 @@ the tracking database on port 5432.
geo_secondary['db_host'] = '<tracking_database_host>'
geo_secondary['db_port'] = <tracking_database_port> # change to the correct port
geo_secondary['db_fdw'] = true # enable FDW
geo_postgresql['enable'] = false # don't use internal managed instance
```
@ -236,48 +232,3 @@ the tracking database on port 5432.
gitlab-rake geo:db:create
gitlab-rake geo:db:migrate
```
1. Configure the [PostgreSQL FDW](https://www.postgresql.org/docs/11/postgres-fdw.html)
connection and credentials:
Save the script below in a file, ex. `/tmp/geo_fdw.sh` and modify the connection
parameters to match your environment. Execute it to set up the FDW connection.
```shell
#!/bin/bash
# Secondary Database connection params:
DB_HOST="<public_ip_or_vpc_private_ip>"
DB_NAME="gitlabhq_production"
DB_USER="gitlab"
DB_PASS="<your_password_here>"
DB_PORT="5432"
# Tracking Database connection params:
GEO_DB_HOST="<public_ip_or_vpc_private_ip>"
GEO_DB_NAME="gitlabhq_geo_production"
GEO_DB_USER="gitlab_geo"
GEO_DB_PORT="5432"
query_exec () {
gitlab-psql -h $GEO_DB_HOST -U $GEO_DB_USER -d $GEO_DB_NAME -p $GEO_DB_PORT -c "${1}"
}
query_exec "CREATE EXTENSION postgres_fdw;"
query_exec "CREATE SERVER gitlab_secondary FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '${DB_HOST}', dbname '${DB_NAME}', port '${DB_PORT}');"
query_exec "CREATE USER MAPPING FOR ${GEO_DB_USER} SERVER gitlab_secondary OPTIONS (user '${DB_USER}', password '${DB_PASS}');"
query_exec "CREATE SCHEMA gitlab_secondary;"
query_exec "GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO ${GEO_DB_USER};"
```
NOTE: **Note:**
The script template above uses `gitlab-psql` as it's intended to be executed from the Geo machine,
but you can change it to `psql` and run it from any machine that has access to the database. We also recommend using
`psql` for AWS RDS.
1. Save the file and [restart GitLab](../../restart_gitlab.md#omnibus-gitlab-restart)
1. Populate the FDW tables:
```shell
gitlab-rake geo:db:refresh_foreign_tables
```

View File

@ -117,7 +117,7 @@ The following are required to run Geo:
The following operating systems are known to ship with a current version of OpenSSH:
- [CentOS](https://www.centos.org) 7.4+
- [Ubuntu](https://ubuntu.com) 16.04+
- PostgreSQL 11+ with [FDW](https://www.postgresql.org/docs/11/postgres-fdw.html) support and [Streaming Replication](https://wiki.postgresql.org/wiki/Streaming_Replication)
- PostgreSQL 11+ with [Streaming Replication](https://wiki.postgresql.org/wiki/Streaming_Replication)
- Git 2.9+
- All nodes must run the same GitLab version.
@ -166,7 +166,6 @@ The tracking database instance is used as metadata to control what needs to be u
- Fetch changes from a repository that has recently been updated.
Because the replicated database instance is read-only, we need this additional database instance for each **secondary** node.
The tracking database requires the `postgres_fdw` extension.
### Geo Log Cursor

View File

@ -260,10 +260,8 @@ Configure the tracking database.
geo_postgresql['sql_user_password'] = '<tracking_database_password_md5_hash>'
##
## Configure FDW connection to the replica database
## Configure PostgreSQL connection to the replica database
##
geo_secondary['db_fdw'] = true
geo_postgresql['fdw_external_password'] = '<replica_database_password_plaintext>'
geo_postgresql['md5_auth_cidr_addresses'] = ['<replica_database_ip>/32']
gitlab_rails['db_host'] = '<replica_database_ip>'

View File

@ -14,7 +14,6 @@ Here is a list of steps you should take to attempt to fix problem:
- Perform [basic troubleshooting](#basic-troubleshooting).
- Fix any [replication errors](#fixing-replication-errors).
- Fix any [Foreign Data Wrapper](#fixing-foreign-data-wrapper-errors) errors.
- Fix any [common](#fixing-common-errors) errors.
## Basic troubleshooting
@ -64,8 +63,6 @@ This machine's Geo node name matches a database record ... yes, found a secondar
GitLab Geo secondary database is correctly configured ... yes
Database replication enabled? ... yes
Database replication working? ... yes
GitLab Geo tracking database is configured to use Foreign Data Wrapper? ... yes
GitLab Geo tracking database Foreign Data Wrapper schema is up-to-date? ... yes
GitLab Geo HTTP(S) connectivity ...
* Can connect to the primary node ... yes
HTTP/HTTPS repository cloning is enabled ... yes
@ -255,7 +252,6 @@ sudo gitlab-rake gitlab:geo:check
When performing a PostgreSQL major version (9 > 10) update this is expected. Follow:
- [initiate-the-replication-process](database.md#step-3-initiate-the-replication-process)
- [Geo database has an outdated FDW remote schema](troubleshooting.md#geo-database-has-an-outdated-fdw-remote-schema-error)
## Fixing replication errors
@ -508,12 +504,6 @@ to start again from scratch, there are a few steps that can help you:
gitlab-ctl start
```
1. Refresh Foreign Data Wrapper tables
```shell
gitlab-rake geo:db:refresh_foreign_tables
```
## Fixing errors during a PostgreSQL upgrade or downgrade
### Message: `ERROR: psql: FATAL: role "gitlab-consul" does not exist`
@ -572,24 +562,6 @@ Then reconfigure GitLab:
sudo gitlab-ctl reconfigure
```
### Message: `ActiveRecord::StatementInvalid: PG::SqlclientUnableToEstablishSqlconnection: ERROR: could not connect to server "gitlab_secondary"`
When
[upgrading PostgreSQL on a Geo instance](https://docs.gitlab.com/omnibus/settings/database.html#upgrading-a-geo-instance), or when downgrading, you
might encounter the following error:
```plaintext
$ sudo gitlab-rake geo:db:refresh_foreign_tables
Refreshing foreign tables for FDW: gitlab_secondary ... rake aborted!
ActiveRecord::StatementInvalid: PG::SqlclientUnableToEstablishSqlconnection: ERROR: could not connect to server "gitlab_secondary"
DETAIL: SSL error: certificate verify failed
FATAL: no pg_hba.conf entry for host "10.138.0.59", user "gitlab", database "gitlabhq_production", SSL off
```
You may need to `gitlab-ctl restart` the read-replica DB node for its PostgreSQL
server to recognize recent changes.
## Fixing errors during a failover or when promoting a secondary to a primary node
The following are possible errors that might be encountered during failover or
@ -702,231 +674,6 @@ sudo /opt/gitlab/embedded/bin/gitlab-pg-ctl promote
GitLab 12.9 and later are [unaffected by this error](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5147).
## Fixing Foreign Data Wrapper errors
This section documents ways to fix potential Foreign Data Wrapper errors.
### "Foreign Data Wrapper (FDW) is not configured" error
When setting up Geo, you might see this warning in the `gitlab-rake
gitlab:geo:check` output:
```plaintext
GitLab Geo tracking database Foreign Data Wrapper schema is up-to-date? ... foreign data wrapper is not configured
```
There are a few key points to remember:
1. The FDW settings are configured on the Geo **tracking** database.
1. The configured foreign server enables a login to the Geo
**secondary**, read-only database.
By default, the Geo secondary and tracking database are running on the
same host on different ports. That is, 5432 and 5431 respectively.
#### Checking configuration
NOTE: **Note:**
The following steps are for Omnibus installs only. Using Geo with source-based installs was **deprecated** in GitLab 11.5.
To check the configuration:
1. SSH into an app node in the **secondary**:
```shell
sudo -i
```
Note: An app node is any machine running at least one of the following services:
- `puma`
- `unicorn`
- `sidekiq`
- `geo-logcursor`
1. Enter the database console:
If the tracking database is running on the same node:
```shell
gitlab-geo-psql
```
Or, if the tracking database is running on a different node, you must specify
the user and host when entering the database console:
```shell
gitlab-geo-psql -U gitlab_geo -h <IP of tracking database>
```
You will be prompted for the password of the `gitlab_geo` user. You can find
it in plaintext in `/etc/gitlab/gitlab.rb` at:
```ruby
geo_secondary['db_password'] = '<geo_tracking_db_password>'
```
This password is normally set on the tracking database during
[Step 3: Configure the tracking database on the secondary node](multiple_servers.md#step-3-configure-the-tracking-database-on-the-secondary-node),
and it is set on the app nodes during
[Step 4: Configure the frontend application servers on the secondary node](multiple_servers.md#step-4-configure-the-frontend-application-servers-on-the-secondary-node).
1. Check whether any tables are present with the following statement:
```sql
SELECT * from information_schema.foreign_tables;
```
If everything is working, you should see something like this:
```plaintext
gitlabhq_geo_production=# SELECT * from information_schema.foreign_tables;
foreign_table_catalog | foreign_table_schema | foreign_table_name | foreign_server_catalog | foreign_server_name
-------------------------+----------------------+-------------------------------------------------+-------------------------+---------------------
gitlabhq_geo_production | gitlab_secondary | abuse_reports | gitlabhq_geo_production | gitlab_secondary
gitlabhq_geo_production | gitlab_secondary | appearances | gitlabhq_geo_production | gitlab_secondary
gitlabhq_geo_production | gitlab_secondary | application_setting_terms | gitlabhq_geo_production | gitlab_secondary
gitlabhq_geo_production | gitlab_secondary | application_settings | gitlabhq_geo_production | gitlab_secondary
<snip>
```
However, if the query returns with `0 rows`, then continue onto the next steps.
1. Check that the foreign server mapping is correct via `\des+`. The
results should look something like this:
```plaintext
gitlabhq_geo_production=# \des+
List of foreign servers
-[ RECORD 1 ]--------+------------------------------------------------------------
Name | gitlab_secondary
Owner | gitlab-psql
Foreign-data wrapper | postgres_fdw
Access privileges | "gitlab-psql"=U/"gitlab-psql" +
| gitlab_geo=U/"gitlab-psql"
Type |
Version |
FDW Options | (host '0.0.0.0', port '5432', dbname 'gitlabhq_production')
Description |
```
NOTE: **Note:**
Pay particular attention to the host and port under
FDW options. That configuration should point to the Geo secondary
database.
If you need to experiment with changing the host or password, the
following queries demonstrate how:
```sql
ALTER SERVER gitlab_secondary OPTIONS (SET host '<my_new_host>');
ALTER SERVER gitlab_secondary OPTIONS (SET port 5432);
```
If you change the host and/or port, you will also have to adjust the
following settings in `/etc/gitlab/gitlab.rb` and run `gitlab-ctl
reconfigure`:
- `gitlab_rails['db_host']`
- `gitlab_rails['db_port']`
1. Check that the user mapping is configured properly via `\deu+`:
```plaintext
gitlabhq_geo_production=# \deu+
List of user mappings
Server | User name | FDW Options
------------------+------------+--------------------------------------------------------------------------------
gitlab_secondary | gitlab_geo | ("user" 'gitlab', password 'YOUR-PASSWORD-HERE')
(1 row)
```
Make sure the password is correct. You can test that logins work by running `psql`:
```shell
# Connect to the tracking database as the `gitlab_geo` user
sudo \
-u git /opt/gitlab/embedded/bin/psql \
-h /var/opt/gitlab/geo-postgresql \
-p 5431 \
-U gitlab_geo \
-W \
-d gitlabhq_geo_production
```
If you need to correct the password, the following query shows how:
```sql
ALTER USER MAPPING FOR gitlab_geo SERVER gitlab_secondary OPTIONS (SET password '<my_new_password>');
```
If you change the user or password, you will also have to adjust the
following settings in `/etc/gitlab/gitlab.rb` and run `gitlab-ctl
reconfigure`:
- `gitlab_rails['db_username']`
- `gitlab_rails['db_password']`
If you are using [PgBouncer in front of the secondary
database](database.md#pgbouncer-support-optional), be sure to update
the following settings:
- `geo_postgresql['fdw_external_user']`
- `geo_postgresql['fdw_external_password']`
#### Manual reload of FDW schema
If you're still unable to get FDW working, you may want to try a manual
reload of the FDW schema. To manually reload the FDW schema:
1. On the node running the Geo tracking database, enter the PostgreSQL console via
the `gitlab_geo` user:
```shell
sudo \
-u git /opt/gitlab/embedded/bin/psql \
-h /var/opt/gitlab/geo-postgresql \
-p 5431 \
-U gitlab_geo \
-W \
-d gitlabhq_geo_production
```
Be sure to adjust the port and hostname for your configuration. You
may be asked to enter a password.
1. Reload the schema via:
```sql
DROP SCHEMA IF EXISTS gitlab_secondary CASCADE;
CREATE SCHEMA gitlab_secondary;
GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO gitlab_geo;
IMPORT FOREIGN SCHEMA public FROM SERVER gitlab_secondary INTO gitlab_secondary;
```
1. Test that queries work:
```sql
SELECT * from information_schema.foreign_tables;
SELECT * FROM gitlab_secondary.projects limit 1;
```
### "Geo database has an outdated FDW remote schema" error
GitLab can error with a `Geo database has an outdated FDW remote schema` message.
For example:
```plaintext
Geo database has an outdated FDW remote schema. It contains 229 of 236 expected tables. Please refer to Geo Troubleshooting.
```
To resolve this, run the following command on the **secondary**:
```shell
sudo gitlab-rake geo:db:refresh_foreign_tables
```
## Expired artifacts
If you notice for some reason there are more artifacts on the Geo
@ -1005,13 +752,6 @@ If you are using Omnibus GitLab installation, something might have failed during
- Run `sudo gitlab-ctl reconfigure`.
- Manually trigger the database migration by running: `sudo gitlab-rake geo:db:migrate` as root on the **secondary** node.
### Geo database is not configured to use Foreign Data Wrapper
This error means the Geo Tracking Database doesn't have the FDW server and credentials
configured.
See ["Foreign Data Wrapper (FDW) is not configured" error?](#foreign-data-wrapper-fdw-is-not-configured-error).
### GitLab indicates that more than 100% of repositories were synced
This can be caused by orphaned records in the project registry. You can clear them

View File

@ -11,6 +11,19 @@ Check this document if it includes instructions for the version you are updating
These steps go together with the [general steps](updating_the_geo_nodes.md#general-update-steps)
for updating Geo nodes.
## Updating to GitLab 13.3
In GitLab 13.3, Geo removed the PostgreSQL [Foreign Data Wrapper](https://www.postgresql.org/docs/11/postgres-fdw.html) dependency for the tracking database.
The FDW server, user, and the extension will be removed during the upgrade process on each **secondary** node. The GitLab settings related to the FDW in the `/etc/gitlab/gitlab.rb` have been deprecated and can be safely removed.
There are some scenarios like using an external PostgreSQL instance for the tracking database where the FDW settings must be removed manually. Enter the PostgreSQL console of that instance and remove them:
```shell
DROP SERVER gitlab_secondary CASCADE;
DROP EXTENSION IF EXISTS postgres_fdw;
```
## Updating to GitLab 13.0
Upgrading to GitLab 13.0 requires GitLab 12.10 to already be using PostgreSQL

View File

@ -58,7 +58,6 @@ This section is for links to information elsewhere in the GitLab documentation.
- Support for MySQL was removed in GitLab 12.1; [migrate to PostgreSQL](../../update/mysql_to_postgresql.md)
- required extension `pg_trgm`
- required extension `btree_gist`
- required extension `postgres_fdw` for Geo
- Errors like this in the `production/sidekiq` log; see: [Set default_transaction_isolation into read committed](https://docs.gitlab.com/omnibus/settings/database.html#set-default_transaction_isolation-into-read-committed):
@ -85,7 +84,7 @@ This section is for links to information elsewhere in the GitLab documentation.
PANIC: could not write to file pg_xlog/xlogtemp.123: No space left on device
```
- [Checking Geo configuration](../geo/replication/troubleshooting.md#checking-configuration) including
- [Checking Geo configuration](../geo/replication/troubleshooting.md) including
- reconfiguring hosts/ports
- checking and fixing user/password mappings

View File

@ -720,7 +720,7 @@ POST /projects/:id/issues
| `title` | string | yes | The title of an issue |
| `description` | string | no | The description of an issue. Limited to 1,048,576 characters. |
| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. |
| `assignee_ids` | integer array | no | The ID of a user to assign issue |
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the issue to. |
| `milestone_id` | integer | no | The global ID of a milestone to assign issue |
| `labels` | string | no | Comma-separated label names for an issue |
| `created_at` | string | no | Date time string, ISO 8601 formatted, for example `2016-03-11T03:45:40Z` (requires admin or project/group owner rights) |

View File

@ -209,121 +209,12 @@ To migrate the tracking database, run:
bundle exec rake geo:db:migrate
```
### Foreign Data Wrapper
> Introduced in GitLab 10.1.
Foreign Data Wrapper ([FDW](#fdw)) is used by the [Geo Log Cursor](#geo-log-cursor) and improves
the performance of many synchronization operations.
FDW is a PostgreSQL extension ([`postgres_fdw`](https://www.postgresql.org/docs/11/postgres-fdw.html)) that is enabled within
the Geo Tracking Database (on a **secondary** node), which allows it
to connect to the read-only database replica and perform queries and filter
data from both instances.
This persistent connection is configured as an FDW server
named `gitlab_secondary`. This configuration exists within the database's user
context only. To access the `gitlab_secondary`, GitLab needs to use the
same database user that had previously been configured.
The Geo Tracking Database accesses the read-only database replica via FDW as a regular user,
limited by its own restrictions. The credentials are configured as a
`USER MAPPING` associated with the `SERVER` mapped previously
(`gitlab_secondary`).
FDW configuration and credentials definition are managed automatically by the
Omnibus GitLab `gitlab-ctl reconfigure` command.
#### Refreshing the Foreign Tables
Whenever a new Geo node is configured or the database schema changes on the
**primary** node, you must refresh the foreign tables on the **secondary** node
by running the following:
```shell
bundle exec rake geo:db:refresh_foreign_tables
```
Failure to do this will prevent the **secondary** node from
functioning properly. The **secondary** node will generate error
messages, as the following PostgreSQL error:
```sql
ERROR: relation "gitlab_secondary.ci_job_artifacts" does not exist at character 323
STATEMENT: SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '"gitlab_secondary"."ci_job_artifacts"'::regclass
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum
```
#### Accessing data from a Foreign Table
At the SQL level, all you have to do is `SELECT` data from `gitlab_secondary.*`.
Here's an example of how to access all projects from the Geo Tracking Database's FDW:
```sql
SELECT * FROM gitlab_secondary.projects;
```
As a more real-world example, this is how you filter for unarchived projects
on the Tracking Database:
```sql
SELECT project_registry.*
FROM project_registry
JOIN gitlab_secondary.projects
ON (project_registry.project_id = gitlab_secondary.projects.id
AND gitlab_secondary.projects.archived IS FALSE)
```
At the ActiveRecord level, we have additional Models that represent the
foreign tables. They must be mapped in a slightly different way, and they are read-only.
Check the existing FDW models in `ee/app/models/geo/fdw` for reference.
From a developer's perspective, it's no different than creating a model that
represents a Database View.
With the examples above, you can access the projects with:
```ruby
Geo::Fdw::Project.all
```
and to access the `ProjectRegistry` filtering by unarchived projects:
```ruby
# We have to use Arel here:
project_registry_table = Geo::ProjectRegistry.arel_table
fdw_project_table = Geo::Fdw::Project.arel_table
project_registry_table.join(fdw_project_table)
.on(project_registry_table[:project_id].eq(fdw_project_table[:id]))
.where((fdw_project_table[:archived]).eq(true)) # if you append `.to_sql` you can check generated query
```
## Finders
Geo uses [Finders](https://gitlab.com/gitlab-org/gitlab/tree/master/app/finders),
which are classes take care of the heavy lifting of looking up
projects/attachments/etc. in the tracking database and main database.
### Finders Performance
The Finders need to compare data from the main database with data in
the tracking database. For example, counting the number of synced
projects normally involves retrieving the project IDs from one
database and checking their state in the other database. This is slow
and requires a lot of memory.
To overcome this, the Finders use [FDW](#fdw), or Foreign Data
Wrappers. This allows a regular `JOIN` between the main database and
the tracking database.
## Redis
Redis on the **secondary** node works the same as on the **primary**
@ -397,12 +288,6 @@ migration do not need to run on the secondary nodes.
A database on each Geo **secondary** node that keeps state for the node
on which it resides. Read more in [Using the Tracking database](#using-the-tracking-database).
### FDW
Foreign Data Wrapper, or FDW, is a feature built-in in PostgreSQL. It
allows data to be queried from different data sources. In Geo, it's
used to query data from different PostgreSQL instances.
## Geo Event Log
The Geo **primary** stores events in the `geo_event_log` table. Each

View File

@ -156,10 +156,6 @@ If you're using [GitLab Geo](../administration/geo/replication/index.md):
- We strongly recommend running Omnibus-managed instances as they are actively
developed and tested. We aim to be compatible with most external (not managed
by Omnibus) databases (for example, [AWS Relational Database Service (RDS)](https://aws.amazon.com/rds/)) but we don't guarantee compatibility.
- You must also ensure the `postgres_fdw` extension is loaded into every
GitLab database. This extension
[can be enabled](https://www.postgresql.org/docs/11/sql-createextension.html)
using a PostgreSQL super user.
## Puma settings

View File

@ -548,13 +548,17 @@ To trigger the re-index from `primary` index:
### Trigger the reindex via the Elasticsearch administration
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34069) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34069) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2.
> - A scheduled index deletion and the ability to cancel it was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38914) in GitLab Starter 13.3.
Under **Admin Area > Integration > Elasticsearch zero-downtime reindexing**, click on **Trigger cluster reindexing**.
NOTE: **Note:**
Reindexing can be a lengthy process depending on the size of your Elasticsearch cluster.
CAUTION: **Caution:**
After the reindexing is completed, the original index will be scheduled to be deleted after 14 days. You can cancel this action by pressing the cancel button.
While the reindexing is running, you will be able to follow its progress under that same section.
## GitLab Elasticsearch Rake tasks

View File

@ -4319,6 +4319,9 @@ msgstr ""
msgid "Cancel"
msgstr ""
msgid "Cancel index deletion"
msgstr ""
msgid "Cancel running"
msgstr ""
@ -13010,6 +13013,9 @@ msgstr ""
msgid "Index all projects"
msgstr ""
msgid "Index deletion is canceled"
msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
@ -26265,6 +26271,9 @@ msgstr ""
msgid "Until"
msgstr ""
msgid "Unused, previous index '%{index_name}' will be deleted after %{time} automatically."
msgstr ""
msgid "Unverified"
msgstr ""

View File

@ -1,21 +1,26 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import JiraImportApp from '~/jira_import/components/jira_import_app.vue';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql';
import getJiraUserMappingMutation from '~/jira_import/queries/get_jira_user_mapping.mutation.graphql';
import { imports, issuesPath, jiraIntegrationPath, jiraProjects, userMappings } from '../mock_data';
import {
imports,
issuesPath,
jiraIntegrationPath,
jiraProjects,
projectId,
projectPath,
} from '../mock_data';
describe('JiraImportApp', () => {
let axiosMock;
let mutateSpy;
let wrapper;
const inProgressIllustration = 'in-progress-illustration.svg';
const setupIllustration = 'setup-illustration.svg';
const getFormComponent = () => wrapper.find(JiraImportForm);
const getProgressComponent = () => wrapper.find(JiraImportProgress);
@ -32,22 +37,19 @@ describe('JiraImportApp', () => {
showAlert = false,
isInProgress = false,
loading = false,
mutate = mutateSpy,
} = {}) =>
shallowMount(JiraImportApp, {
propsData: {
inProgressIllustration: 'in-progress-illustration.svg',
inProgressIllustration,
isJiraConfigured,
issuesPath,
jiraIntegrationPath,
projectId: '5',
projectPath: 'gitlab-org/gitlab-test',
setupIllustration: 'setup-illustration.svg',
projectId,
projectPath,
setupIllustration,
},
data() {
return {
isSubmitting: false,
userMappings,
errorMessage,
showAlert,
jiraImportDetails: {
@ -61,26 +63,11 @@ describe('JiraImportApp', () => {
mocks: {
$apollo: {
loading,
mutate,
},
},
});
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
mutateSpy = jest.fn(() =>
Promise.resolve({
data: {
jiraImportStart: { errors: [] },
jiraImportUsers: { jiraUsers: [], errors: [] },
},
}),
);
});
afterEach(() => {
axiosMock.restore();
mutateSpy.mockRestore();
wrapper.destroy();
wrapper = null;
});
@ -173,72 +160,79 @@ describe('JiraImportApp', () => {
});
});
describe('import in progress screen', () => {
describe('import setup component', () => {
beforeEach(() => {
wrapper = mountComponent({ isJiraConfigured: false });
});
it('receives the illustration', () => {
expect(getSetupComponent().props('illustration')).toBe(setupIllustration);
});
it('receives the path to the Jira integration page', () => {
expect(getSetupComponent().props('jiraIntegrationPath')).toBe(jiraIntegrationPath);
});
});
describe('import in progress component', () => {
beforeEach(() => {
wrapper = mountComponent({ isInProgress: true });
});
it('shows the illustration', () => {
expect(getProgressComponent().props('illustration')).toBe('in-progress-illustration.svg');
it('receives the illustration', () => {
expect(getProgressComponent().props('illustration')).toBe(inProgressIllustration);
});
it('shows the name of the most recent import initiator', () => {
it('receives the name of the most recent import initiator', () => {
expect(getProgressComponent().props('importInitiator')).toBe('Jane Doe');
});
it('shows the name of the most recent imported project', () => {
it('receives the name of the most recent imported project', () => {
expect(getProgressComponent().props('importProject')).toBe('MTG');
});
it('shows the time of the most recent import', () => {
it('receives the time of the most recent import', () => {
expect(getProgressComponent().props('importTime')).toBe('2020-04-09T16:17:18+00:00');
});
it('has the path to the issues page', () => {
it('receives the path to the issues page', () => {
expect(getProgressComponent().props('issuesPath')).toBe('gitlab-org/gitlab-test/-/issues');
});
});
describe('initiating a Jira import', () => {
it('calls the mutation with the expected arguments', () => {
describe('import form component', () => {
beforeEach(() => {
wrapper = mountComponent();
const mutationArguments = {
mutation: initiateJiraImportMutation,
variables: {
input: {
jiraProjectKey: 'MTG',
projectPath: 'gitlab-org/gitlab-test',
usersMapping: [
{
jiraAccountId: 'aei23f98f-q23fj98qfj',
gitlabId: 15,
},
{
jiraAccountId: 'fu39y8t34w-rq3u289t3h4i',
gitlabId: undefined,
},
],
},
},
};
getFormComponent().vm.$emit('initiateJiraImport', 'MTG');
expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
});
describe('when there is an error', () => {
beforeEach(() => {
const mutate = jest.fn(() => Promise.reject());
wrapper = mountComponent({ mutate });
it('receives the illustration', () => {
expect(getFormComponent().props('issuesPath')).toBe(issuesPath);
});
getFormComponent().vm.$emit('initiateJiraImport', 'MTG');
});
it('receives the name of the most recent import initiator', () => {
expect(getFormComponent().props('jiraImports')).toEqual(imports);
});
it('shows alert message with error message', async () => {
expect(getAlert().text()).toBe('There was an error importing the Jira project.');
});
it('receives the name of the most recent imported project', () => {
expect(getFormComponent().props('jiraProjects')).toEqual(jiraProjects);
});
it('receives the project ID', () => {
expect(getFormComponent().props('projectId')).toBe(projectId);
});
it('receives the project path', () => {
expect(getFormComponent().props('projectPath')).toBe(projectPath);
});
it('shows an alert when it emits an error', async () => {
expect(getAlert().exists()).toBe(false);
getFormComponent().vm.$emit('error', 'There was an error');
await Vue.nextTick();
expect(getAlert().exists()).toBe(true);
});
});
@ -259,40 +253,4 @@ describe('JiraImportApp', () => {
expect(getAlert().exists()).toBe(false);
});
});
describe('on mount GraphQL user mapping mutation', () => {
it('is called with the expected arguments', () => {
wrapper = mountComponent();
const mutationArguments = {
mutation: getJiraUserMappingMutation,
variables: {
input: {
projectPath: 'gitlab-org/gitlab-test',
},
},
};
expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
});
describe('when Jira is not configured', () => {
it('is not called', () => {
wrapper = mountComponent({ isJiraConfigured: false });
expect(mutateSpy).not.toHaveBeenCalled();
});
});
describe('when there is an error when called', () => {
beforeEach(() => {
const mutate = jest.fn(() => Promise.reject());
wrapper = mountComponent({ mutate });
});
it('shows error message', () => {
expect(getAlert().exists()).toBe(true);
});
});
});
});

View File

@ -4,15 +4,20 @@ import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
import getJiraUserMappingMutation from '~/jira_import/queries/get_jira_user_mapping.mutation.graphql';
import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql';
import {
imports,
issuesPath,
jiraProjects,
projectId,
projectPath,
userMappings as defaultUserMappings,
} from '../mock_data';
describe('JiraImportForm', () => {
let axiosMock;
let mutateSpy;
let wrapper;
const currentUsername = 'mrgitlab';
@ -35,35 +40,53 @@ describe('JiraImportForm', () => {
const mountComponent = ({
isSubmitting = false,
loading = false,
mutate = mutateSpy,
selectedProject = 'MTG',
userMappings = defaultUserMappings,
mountFunction = shallowMount,
} = {}) =>
mountFunction(JiraImportForm, {
propsData: {
isSubmitting,
issuesPath,
jiraImports: imports,
jiraProjects,
projectId: '5',
userMappings,
projectId,
projectPath,
},
data: () => ({
isFetching: false,
isSubmitting,
searchTerm: '',
selectedProject,
selectState: null,
users: [],
userMappings,
}),
mocks: {
$apollo: {
loading,
mutate,
},
},
currentUsername,
});
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
mutateSpy = jest.fn(() =>
Promise.resolve({
data: {
jiraImportStart: { errors: [] },
jiraImportUsers: { jiraUsers: [], errors: [] },
},
}),
);
});
afterEach(() => {
axiosMock.restore();
mutateSpy.mockRestore();
wrapper.destroy();
wrapper = null;
});
@ -238,15 +261,61 @@ describe('JiraImportForm', () => {
});
});
describe('form', () => {
it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => {
const selectedProject = 'MTG';
describe('submitting the form', () => {
it('initiates the Jira import mutation with the expected arguments', () => {
wrapper = mountComponent();
wrapper = mountComponent({ selectedProject });
const mutationArguments = {
mutation: initiateJiraImportMutation,
variables: {
input: {
jiraProjectKey: 'MTG',
projectPath,
usersMapping: [
{
jiraAccountId: 'aei23f98f-q23fj98qfj',
gitlabId: 15,
},
{
jiraAccountId: 'fu39y8t34w-rq3u289t3h4i',
gitlabId: undefined,
},
],
},
},
};
wrapper.find('form').trigger('submit');
expect(wrapper.emitted('initiateJiraImport')[0]).toEqual([selectedProject]);
expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
});
});
describe('on mount GraphQL user mapping mutation', () => {
it('is called with the expected arguments', () => {
wrapper = mountComponent();
const mutationArguments = {
mutation: getJiraUserMappingMutation,
variables: {
input: {
projectPath,
},
},
};
expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
});
describe('when there is an error when called', () => {
beforeEach(() => {
const mutate = jest.fn(() => Promise.reject());
wrapper = mountComponent({ mutate });
});
it('shows error message', () => {
expect(getAlert().exists()).toBe(true);
});
});
});
});

View File

@ -3,6 +3,16 @@ import { IMPORT_STATE } from '~/jira_import/utils/jira_import_utils';
export const fullPath = 'gitlab-org/gitlab-test';
export const issuesPath = 'gitlab-org/gitlab-test/-/issues';
export const illustration = 'illustration.svg';
export const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit';
export const projectId = '5';
export const projectPath = 'gitlab-org/gitlab-test';
export const queryDetails = {
query: getJiraImportDetailsQuery,
variables: {
@ -71,12 +81,6 @@ export const jiraImportMutationResponse = {
},
};
export const issuesPath = 'gitlab-org/gitlab-test/-/issues';
export const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit';
export const illustration = 'illustration.svg';
export const jiraProjects = [
{ text: 'My Jira Project (MJP)', value: 'MJP' },
{ text: 'My Second Jira Project (MSJP)', value: 'MSJP' },