diff options
33 files changed, 748 insertions, 281 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c15f7b1d784..1615c30108d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 13.5.1 (2020-10-22) + +### Other (1 change) + +- Update GitLab Shell to v13.11.0. !45660 + + ## 13.5.0 (2020-10-22) ### Security (1 change) diff --git a/app/assets/javascripts/analytics/instance_statistics/components/app.vue b/app/assets/javascripts/analytics/instance_statistics/components/app.vue index 7aa5c98aa0b..2533854119f 100644 --- a/app/assets/javascripts/analytics/instance_statistics/components/app.vue +++ b/app/assets/javascripts/analytics/instance_statistics/components/app.vue @@ -1,19 +1,63 @@ <script> +import { s__ } from '~/locale'; import InstanceCounts from './instance_counts.vue'; -import PipelinesChart from './pipelines_chart.vue'; +import InstanceStatisticsCountChart from './instance_statistics_count_chart.vue'; import UsersChart from './users_chart.vue'; +import pipelinesStatsQuery from '../graphql/queries/pipeline_stats.query.graphql'; +import issuesAndMergeRequestsQuery from '../graphql/queries/issues_and_merge_requests.query.graphql'; import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants'; +const PIPELINES_KEY_TO_NAME_MAP = { + total: s__('InstanceAnalytics|Total'), + succeeded: s__('InstanceAnalytics|Succeeded'), + failed: s__('InstanceAnalytics|Failed'), + canceled: s__('InstanceAnalytics|Canceled'), + skipped: s__('InstanceAnalytics|Skipped'), +}; +const ISSUES_AND_MERGE_REQUESTS_KEY_TO_NAME_MAP = { + issues: s__('InstanceAnalytics|Issues'), + mergeRequests: s__('InstanceAnalytics|Merge Requests'), +}; +const loadPipelineChartError = s__( + 'InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again.', +); +const loadIssuesAndMergeRequestsChartError = s__( + 'InstanceAnalytics|Could not load the issues and merge requests chart. Please refresh the page to try again.', +); +const noDataMessage = s__('InstanceAnalytics|There is no data available.'); + export default { name: 'InstanceStatisticsApp', components: { InstanceCounts, - PipelinesChart, + InstanceStatisticsCountChart, UsersChart, }, TOTAL_DAYS_TO_SHOW, START_DATE, TODAY, + configs: [ + { + keyToNameMap: PIPELINES_KEY_TO_NAME_MAP, + prefix: 'pipelines', + loadChartError: loadPipelineChartError, + noDataMessage, + chartTitle: s__('InstanceAnalytics|Pipelines'), + yAxisTitle: s__('InstanceAnalytics|Items'), + xAxisTitle: s__('InstanceAnalytics|Month'), + query: pipelinesStatsQuery, + }, + { + keyToNameMap: ISSUES_AND_MERGE_REQUESTS_KEY_TO_NAME_MAP, + prefix: 'issuesAndMergeRequests', + loadChartError: loadIssuesAndMergeRequestsChartError, + noDataMessage, + chartTitle: s__('InstanceAnalytics|Issues & Merge Requests'), + yAxisTitle: s__('InstanceAnalytics|Items'), + xAxisTitle: s__('InstanceAnalytics|Month'), + query: issuesAndMergeRequestsQuery, + }, + ], }; </script> @@ -25,6 +69,17 @@ export default { :end-date="$options.TODAY" :total-data-points="$options.TOTAL_DAYS_TO_SHOW" /> - <pipelines-chart /> + <instance-statistics-count-chart + v-for="chartOptions in $options.configs" + :key="chartOptions.chartTitle" + :prefix="chartOptions.prefix" + :key-to-name-map="chartOptions.keyToNameMap" + :query="chartOptions.query" + :x-axis-title="chartOptions.xAxisTitle" + :y-axis-title="chartOptions.yAxisTitle" + :load-chart-error-message="chartOptions.loadChartError" + :no-data-message="chartOptions.noDataMessage" + :chart-title="chartOptions.chartTitle" + /> </div> </template> diff --git a/app/assets/javascripts/analytics/instance_statistics/components/pipelines_chart.vue b/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue index b16d960402b..740af834618 100644 --- a/app/assets/javascripts/analytics/instance_statistics/components/pipelines_chart.vue +++ b/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue @@ -1,29 +1,19 @@ <script> import { GlLineChart } from '@gitlab/ui/dist/charts'; import { GlAlert } from '@gitlab/ui'; -import { mapKeys, mapValues, pick, some, sum } from 'lodash'; +import { mapValues, some, sum } from 'lodash'; import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue'; -import { s__ } from '~/locale'; import { differenceInMonths, formatDateAsMonth, getDayDifference, } from '~/lib/utils/datetime_utility'; +import { convertToTitleCase } from '~/lib/utils/text_utility'; import { getAverageByMonth, sortByDate, extractValues } from '../utils'; -import pipelineStatsQuery from '../graphql/queries/pipeline_stats.query.graphql'; import { TODAY, START_DATE } from '../constants'; -const DATA_KEYS = [ - 'pipelinesTotal', - 'pipelinesSucceeded', - 'pipelinesFailed', - 'pipelinesCanceled', - 'pipelinesSkipped', -]; -const PREFIX = 'pipelines'; - export default { - name: 'PipelinesChart', + name: 'InstanceStatisticsCountChart', components: { GlLineChart, GlAlert, @@ -31,19 +21,42 @@ export default { }, startDate: START_DATE, endDate: TODAY, - i18n: { - loadPipelineChartError: s__( - 'InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again.', - ), - noDataMessage: s__('InstanceAnalytics|There is no data available.'), - total: s__('InstanceAnalytics|Total'), - succeeded: s__('InstanceAnalytics|Succeeded'), - failed: s__('InstanceAnalytics|Failed'), - canceled: s__('InstanceAnalytics|Canceled'), - skipped: s__('InstanceAnalytics|Skipped'), - chartTitle: s__('InstanceAnalytics|Pipelines'), - yAxisTitle: s__('InstanceAnalytics|Items'), - xAxisTitle: s__('InstanceAnalytics|Month'), + dataKey: 'nodes', + pageInfoKey: 'pageInfo', + firstKey: 'first', + props: { + prefix: { + type: String, + required: true, + }, + keyToNameMap: { + type: Object, + required: true, + }, + chartTitle: { + type: String, + required: true, + }, + loadChartErrorMessage: { + type: String, + required: true, + }, + noDataMessage: { + type: String, + required: true, + }, + xAxisTitle: { + type: String, + required: true, + }, + yAxisTitle: { + type: String, + required: true, + }, + query: { + type: Object, + required: true, + }, }, data() { return { @@ -53,19 +66,23 @@ export default { }, apollo: { pipelineStats: { - query: pipelineStatsQuery, + query() { + return this.query; + }, variables() { - return { - firstTotal: this.totalDaysToShow, - firstSucceeded: this.totalDaysToShow, - firstFailed: this.totalDaysToShow, - firstCanceled: this.totalDaysToShow, - firstSkipped: this.totalDaysToShow, - }; + return this.nameKeys.reduce((memo, key) => { + const firstKey = `${this.$options.firstKey}${convertToTitleCase(key)}`; + return { ...memo, [firstKey]: this.totalDaysToShow }; + }, {}); }, update(data) { - const allData = extractValues(data, DATA_KEYS, PREFIX, 'nodes'); - const allPageInfo = extractValues(data, DATA_KEYS, PREFIX, 'pageInfo'); + const allData = extractValues(data, this.nameKeys, this.prefix, this.$options.dataKey); + const allPageInfo = extractValues( + data, + this.nameKeys, + this.prefix, + this.$options.pageInfoKey, + ); return { ...mapValues(allData, sortByDate), @@ -83,6 +100,9 @@ export default { }, }, computed: { + nameKeys() { + return Object.keys(this.keyToNameMap); + }, isLoading() { return this.$apollo.queries.pipelineStats.loading; }, @@ -90,37 +110,35 @@ export default { return getDayDifference(this.$options.startDate, this.$options.endDate); }, firstVariables() { - const allData = pick(this.pipelineStats, [ - 'nodesTotal', - 'nodesSucceeded', - 'nodesFailed', - 'nodesCanceled', - 'nodesSkipped', - ]); - const allDayDiffs = mapValues(allData, data => { - const firstdataPoint = data[0]; - if (!firstdataPoint) { - return 0; + const firstDataPoints = extractValues( + this.pipelineStats, + this.nameKeys, + this.$options.dataKey, + '[0].recordedAt', + { renameKey: this.$options.firstKey }, + ); + + return Object.keys(firstDataPoints).reduce((memo, name) => { + const recordedAt = firstDataPoints[name]; + if (!recordedAt) { + return { ...memo, [name]: 0 }; } - return Math.max( + const numberOfDays = Math.max( 0, - getDayDifference(this.$options.startDate, new Date(firstdataPoint.recordedAt)), + getDayDifference(this.$options.startDate, new Date(recordedAt)), ); - }); - return mapKeys(allDayDiffs, (value, key) => key.replace('nodes', 'first')); + return { ...memo, [name]: numberOfDays }; + }, {}); }, cursorVariables() { - const pageInfoKeys = [ - 'pageInfoTotal', - 'pageInfoSucceeded', - 'pageInfoFailed', - 'pageInfoCanceled', - 'pageInfoSkipped', - ]; - - return extractValues(this.pipelineStats, pageInfoKeys, 'pageInfo', 'endCursor'); + return extractValues( + this.pipelineStats, + this.nameKeys, + this.$options.pageInfoKey, + 'endCursor', + ); }, hasNextPage() { return ( @@ -132,19 +150,13 @@ export default { return this.chartData.every(({ data }) => data.length === 0); }, chartData() { - const allData = pick(this.pipelineStats, [ - 'nodesTotal', - 'nodesSucceeded', - 'nodesFailed', - 'nodesCanceled', - 'nodesSkipped', - ]); const options = { shouldRound: true }; - return Object.keys(allData).map(key => { - const i18nName = key.slice('nodes'.length).toLowerCase(); + + return this.nameKeys.map(key => { + const dataKey = `${this.$options.dataKey}${convertToTitleCase(key)}`; return { - name: this.$options.i18n[i18nName], - data: getAverageByMonth(allData[key], options), + name: this.keyToNameMap[key], + data: getAverageByMonth(this.pipelineStats?.[dataKey], options), }; }); }, @@ -155,11 +167,11 @@ export default { }; }, chartOptions() { - const { endDate, startDate, i18n } = this.$options; + const { endDate, startDate } = this.$options; return { xAxis: { ...this.range, - name: i18n.xAxisTitle, + name: this.xAxisTitle, type: 'time', splitNumber: differenceInMonths(startDate, endDate) + 1, axisLabel: { @@ -171,7 +183,7 @@ export default { }, }, yAxis: { - name: i18n.yAxisTitle, + name: this.yAxisTitle, }, }; }, @@ -202,13 +214,13 @@ export default { </script> <template> <div> - <h3>{{ $options.i18n.chartTitle }}</h3> + <h3>{{ chartTitle }}</h3> <gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3"> - {{ this.$options.i18n.loadPipelineChartError }} + {{ loadChartErrorMessage }} </gl-alert> <chart-skeleton-loader v-else-if="isLoading" /> <gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3"> - {{ $options.i18n.noDataMessage }} + {{ noDataMessage }} </gl-alert> <gl-line-chart v-else :option="chartOptions" :include-legend-avg-max="true" :data="chartData" /> </div> diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/issues_and_merge_requests.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/issues_and_merge_requests.query.graphql new file mode 100644 index 00000000000..96f21403b34 --- /dev/null +++ b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/issues_and_merge_requests.query.graphql @@ -0,0 +1,34 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" +#import "./count.fragment.graphql" + +query issuesAndMergeRequests( + $firstIssues: Int + $firstMergeRequests: Int + $endCursorIssues: String + $endCursorMergeRequests: String +) { + issuesAndMergeRequestsIssues: instanceStatisticsMeasurements( + identifier: ISSUES + first: $firstIssues + after: $endCursorIssues + ) { + nodes { + ...Count + } + pageInfo { + ...PageInfo + } + } + issuesAndMergeRequestsMergeRequests: instanceStatisticsMeasurements( + identifier: MERGE_REQUESTS + first: $firstMergeRequests + after: $endCursorMergeRequests + ) { + nodes { + ...Count + } + pageInfo { + ...PageInfo + } + } +} diff --git a/app/assets/javascripts/analytics/instance_statistics/utils.js b/app/assets/javascripts/analytics/instance_statistics/utils.js index 907482c0c72..eef66165945 100644 --- a/app/assets/javascripts/analytics/instance_statistics/utils.js +++ b/app/assets/javascripts/analytics/instance_statistics/utils.js @@ -1,6 +1,7 @@ import { masks } from 'dateformat'; -import { mapKeys, mapValues, pick, sortBy } from 'lodash'; +import { get, sortBy } from 'lodash'; import { formatDate } from '~/lib/utils/datetime_utility'; +import { convertToTitleCase } from '~/lib/utils/text_utility'; const { isoDate } = masks; @@ -46,16 +47,25 @@ export function getAverageByMonth(items = [], options = {}) { * const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' }; * extractValues(data, ['fooBar'], 'foo', 'baz') => { bazBar: 'quis' } * @param {Object} data set to extract values from - * @param {Array} dataKeys keys describing where to look for values in the data set - * @param {String} replaceKey name key to be replaced in the data set + * @param {Array} nameKeys keys describing where to look for values in the data set + * @param {String} dataPrefix prefix to `nameKey` on where to get the data * @param {String} nestedKey key nested in the data set to be extracted, * this is also used to rename the newly created data set + * @param {Object} options + * @param {String} options.renameKey? optional rename key, if not provided nestedKey will be used * @return {Object} the newly created data set with the extracted values */ -export function extractValues(data, dataKeys = [], replaceKey, nestedKey) { - return mapKeys(pick(mapValues(data, nestedKey), dataKeys), (value, key) => - key.replace(replaceKey, nestedKey), - ); +export function extractValues(data, nameKeys = [], dataPrefix, nestedKey, options = {}) { + const { renameKey = nestedKey } = options; + + return nameKeys.reduce((memo, name) => { + const titelCaseName = convertToTitleCase(name); + const dataKey = `${dataPrefix}${titelCaseName}`; + const newKey = `${renameKey}${titelCaseName}`; + const itemData = get(data[dataKey], nestedKey); + + return { ...memo, [newKey]: itemData }; + }, {}); } /** diff --git a/app/assets/javascripts/sourcegraph/index.js b/app/assets/javascripts/sourcegraph/index.js index 796e90bf08e..27b63a217d3 100644 --- a/app/assets/javascripts/sourcegraph/index.js +++ b/app/assets/javascripts/sourcegraph/index.js @@ -1,3 +1,5 @@ +import { joinPaths } from '~/lib/utils/url_utility'; + function loadScript(path) { const script = document.createElement('script'); script.type = 'application/javascript'; @@ -17,10 +19,11 @@ export default function initSourcegraph() { return; } - const assetsUrl = new URL('/assets/webpack/sourcegraph/', window.location.href); - const scriptPath = new URL('scripts/integration.bundle.js', assetsUrl).href; + const base = gon.asset_host || gon.gitlab_url; + const assetsUrl = joinPaths(base, '/assets/webpack/sourcegraph/'); + const scriptPath = joinPaths(assetsUrl, 'scripts/integration.bundle.js'); - window.SOURCEGRAPH_ASSETS_URL = assetsUrl.href; + window.SOURCEGRAPH_ASSETS_URL = assetsUrl; window.SOURCEGRAPH_URL = url; window.SOURCEGRAPH_INTEGRATION = 'gitlab-integration'; diff --git a/changelogs/unreleased/233387-remove-lc-temp-index.yml b/changelogs/unreleased/233387-remove-lc-temp-index.yml new file mode 100644 index 00000000000..ef28429f6fc --- /dev/null +++ b/changelogs/unreleased/233387-remove-lc-temp-index.yml @@ -0,0 +1,5 @@ +--- +title: Remove temp index on job artifacts +merge_request: 45565 +author: +type: changed diff --git a/changelogs/unreleased/241267-add-partitioned-table-model.yml b/changelogs/unreleased/241267-add-partitioned-table-model.yml new file mode 100644 index 00000000000..aabf39530d8 --- /dev/null +++ b/changelogs/unreleased/241267-add-partitioned-table-model.yml @@ -0,0 +1,5 @@ +--- +title: Add database view for partitioned tables +merge_request: 45591 +author: +type: other diff --git a/changelogs/unreleased/268248-remove-index-service-for-usage-data.yml b/changelogs/unreleased/268248-remove-index-service-for-usage-data.yml new file mode 100644 index 00000000000..528318f4656 --- /dev/null +++ b/changelogs/unreleased/268248-remove-index-service-for-usage-data.yml @@ -0,0 +1,5 @@ +--- +title: Remove unnecessary index on services for usage data +merge_request: 45655 +author: +type: other diff --git a/changelogs/unreleased/update-gitlab-shell-13-11-0.yml b/changelogs/unreleased/update-gitlab-shell-13-11-0.yml deleted file mode 100644 index 53dbd63b8cb..00000000000 --- a/changelogs/unreleased/update-gitlab-shell-13-11-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update GitLab Shell to v13.11.0 -merge_request: 45660 -author: -type: other diff --git a/db/migrate/20201019130244_remove_license_compliance_temp_index.rb b/db/migrate/20201019130244_remove_license_compliance_temp_index.rb new file mode 100644 index 00000000000..d0383d999ed --- /dev/null +++ b/db/migrate/20201019130244_remove_license_compliance_temp_index.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class RemoveLicenseComplianceTempIndex < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + INDEX_NAME = 'index_ci_job_artifacts_on_license_compliance_file_types' + + disable_ddl_transaction! + + def up + remove_concurrent_index_by_name :ci_job_artifacts, name: INDEX_NAME + end + + def down + add_concurrent_index :ci_job_artifacts, [:job_id, :file_type], where: 'file_type = 10 OR file_type = 101', name: INDEX_NAME + end +end diff --git a/db/migrate/20201019161924_add_partitioned_table_view.rb b/db/migrate/20201019161924_add_partitioned_table_view.rb new file mode 100644 index 00000000000..45bbfda40ff --- /dev/null +++ b/db/migrate/20201019161924_add_partitioned_table_view.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class AddPartitionedTableView < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def up + execute(<<~SQL) + CREATE OR REPLACE VIEW postgres_partitioned_tables AS + SELECT + pg_namespace.nspname::text || '.'::text || pg_class.relname::text AS identifier, + pg_class.oid AS oid, + pg_namespace.nspname AS schema, + pg_class.relname AS name, + CASE partitioned_tables.partstrat + WHEN 'l' THEN 'list' + WHEN 'r' THEN 'range' + WHEN 'h' THEN 'hash' + END as strategy, + array_agg(pg_attribute.attname) as key_columns + FROM ( + SELECT + partrelid, + partstrat, + unnest(partattrs) as column_position + FROM pg_partitioned_table + ) partitioned_tables + INNER JOIN pg_class + ON partitioned_tables.partrelid = pg_class.oid + INNER JOIN pg_namespace + ON pg_class.relnamespace = pg_namespace.oid + INNER JOIN pg_attribute + ON pg_attribute.attrelid = pg_class.oid + AND pg_attribute.attnum = partitioned_tables.column_position + WHERE pg_namespace.nspname = current_schema() + GROUP BY identifier, pg_class.oid, schema, name, strategy; + SQL + end + + def down + execute(<<~SQL) + DROP VIEW IF EXISTS postgres_partitioned_tables + SQL + end +end diff --git a/db/post_migrate/20201020102551_remove_index_service_for_usage_data.rb b/db/post_migrate/20201020102551_remove_index_service_for_usage_data.rb new file mode 100644 index 00000000000..a0d39ecd2c1 --- /dev/null +++ b/db/post_migrate/20201020102551_remove_index_service_for_usage_data.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class RemoveIndexServiceForUsageData < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + INDEX_NAME = 'index_services_on_type_id_when_active_not_instance_not_template' + + disable_ddl_transaction! + + def up + remove_concurrent_index_by_name :services, INDEX_NAME + end + + def down + add_concurrent_index :services, [:type, :id], where: 'active = TRUE AND instance = FALSE AND template = FALSE', name: INDEX_NAME + end +end diff --git a/db/schema_migrations/20201019130244 b/db/schema_migrations/20201019130244 new file mode 100644 index 00000000000..b37e6eeae70 --- /dev/null +++ b/db/schema_migrations/20201019130244 @@ -0,0 +1 @@ +fcd2cc46dae4f4da96ac5a2100a9de9bd360defee82a9c9d6cfbda0e1507ee66
\ No newline at end of file diff --git a/db/schema_migrations/20201019161924 b/db/schema_migrations/20201019161924 new file mode 100644 index 00000000000..3857a5cb5a1 --- /dev/null +++ b/db/schema_migrations/20201019161924 @@ -0,0 +1 @@ +47aba29a35e24113c9f198c731ba95597a2a6cd5d16b01a958644ce3e1a96170
\ No newline at end of file diff --git a/db/schema_migrations/20201020102551 b/db/schema_migrations/20201020102551 new file mode 100644 index 00000000000..f658f1fdc0a --- /dev/null +++ b/db/schema_migrations/20201020102551 @@ -0,0 +1 @@ +ba16ad5a51494f436dc47ffb924f94ce7795bfd3b9061ca58d1c8dda238245e9
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index fc3d1dbef59..e22f21b309e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14645,6 +14645,34 @@ CREATE VIEW postgres_indexes AS JOIN pg_indexes ON ((pg_class.relname = pg_indexes.indexname))) WHERE ((pg_namespace.nspname <> 'pg_catalog'::name) AND (pg_namespace.nspname = ANY (ARRAY["current_schema"(), 'gitlab_partitions_dynamic'::name, 'gitlab_partitions_static'::name]))); +CREATE VIEW postgres_partitioned_tables AS + SELECT (((pg_namespace.nspname)::text || '.'::text) || (pg_class.relname)::text) AS identifier, + pg_class.oid, + pg_namespace.nspname AS schema, + pg_class.relname AS name, + CASE partitioned_tables.partstrat + WHEN 'l'::"char" THEN 'list'::text + WHEN 'r'::"char" THEN 'range'::text + WHEN 'h'::"char" THEN 'hash'::text + ELSE NULL::text + END AS strategy, + array_agg(pg_attribute.attname) AS key_columns + FROM (((( SELECT pg_partitioned_table.partrelid, + pg_partitioned_table.partstrat, + unnest(pg_partitioned_table.partattrs) AS column_position + FROM pg_partitioned_table) partitioned_tables + JOIN pg_class ON ((partitioned_tables.partrelid = pg_class.oid))) + JOIN pg_namespace ON ((pg_class.relnamespace = pg_namespace.oid))) + JOIN pg_attribute ON (((pg_attribute.attrelid = pg_class.oid) AND (pg_attribute.attnum = partitioned_tables.column_position)))) + WHERE (pg_namespace.nspname = "current_schema"()) + GROUP BY (((pg_namespace.nspname)::text || '.'::text) || (pg_class.relname)::text), pg_class.oid, pg_namespace.nspname, pg_class.relname, + CASE partitioned_tables.partstrat + WHEN 'l'::"char" THEN 'list'::text + WHEN 'r'::"char" THEN 'range'::text + WHEN 'h'::"char" THEN 'hash'::text + ELSE NULL::text + END; + CREATE TABLE postgres_reindex_actions ( id bigint NOT NULL, action_start timestamp with time zone NOT NULL, @@ -20030,8 +20058,6 @@ CREATE INDEX index_ci_job_artifacts_on_file_store ON ci_job_artifacts USING btre CREATE UNIQUE INDEX index_ci_job_artifacts_on_job_id_and_file_type ON ci_job_artifacts USING btree (job_id, file_type); -CREATE INDEX index_ci_job_artifacts_on_license_compliance_file_types ON ci_job_artifacts USING btree (job_id, file_type) WHERE ((file_type = 10) OR (file_type = 101)); - CREATE INDEX index_ci_job_artifacts_on_project_id ON ci_job_artifacts USING btree (project_id); CREATE INDEX index_ci_job_artifacts_on_project_id_for_security_reports ON ci_job_artifacts USING btree (project_id) WHERE (file_type = ANY (ARRAY[5, 6, 7, 8])); @@ -21570,8 +21596,6 @@ CREATE UNIQUE INDEX index_services_on_type_and_template_partial ON services USIN CREATE INDEX index_services_on_type_id_when_active_and_project_id_not_null ON services USING btree (type, id) WHERE ((active = true) AND (project_id IS NOT NULL)); -CREATE INDEX index_services_on_type_id_when_active_not_instance_not_template ON services USING btree (type, id) WHERE ((active = true) AND (instance = false) AND (template = false)); - CREATE UNIQUE INDEX index_services_on_unique_group_id_and_type ON services USING btree (group_id, type); CREATE UNIQUE INDEX index_shards_on_name ON shards USING btree (name); diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md index aa6e6f73a0d..d3b0267a7b7 100644 --- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md +++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md @@ -14,7 +14,7 @@ environment, reducing the effort to assess the impact of changes. Thus, when we and it will immediately be clear that the application can still be properly built and deployed. After all, you can _see_ it running! -<img src="img/deployed_dependency_update.png" alt="dependencies.io" class="image-noshadow"> +<img src="img/deployed_dependency_update.png" alt="dependencies.io"> However, looking at the freshly deployed code to check whether it still looks and behaves as expected is repetitive manual work, which means it is a prime candidate for automation. This is diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index 22258f0606e..032b0713ca3 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -48,7 +48,7 @@ documentation. ### All information Include problem-solving actions that may address rare cases or be considered -*risky*, so long as proper context is provided in the form of fully detailed +_risky_, so long as proper context is provided in the form of fully detailed warnings and caveats. This kind of content should be included as it could be helpful to others and, when properly explained, its benefits outweigh the risks. If you think you have found an exception to this rule, contact the @@ -101,9 +101,8 @@ of truth and explain why it is important to consume the information. ### Organize by topic, not by type Beyond top-level audience-type folders (for example, `administration`), we -organize content by topic, not by type, so it can be located as easily as -possible within the single-source-of-truth (SSOT) section for the subject -matter. +organize content by topic, not by type, so it can be located in the +single-source-of-truth (SSOT) section for the subject matter. For example, do not create groupings of similar media types. For example: @@ -118,7 +117,7 @@ cross-link between any related content. ### Docs-first methodology -We employ a *documentation-first methodology* to help ensure the documentation +We employ a _documentation-first methodology_ to help ensure the documentation remains a complete and trusted resource, and to make communicating about the use of GitLab more efficient. @@ -127,8 +126,7 @@ of GitLab more efficient. - When you encounter new information not available in GitLab’s documentation (for example, when working on a support case or testing a feature), your first step should be to create a merge request (MR) to add this information to the - documentation. You can then share the MR in order to communicate this - information. + documentation. You can then share the MR to communicate this information. New information that would be useful toward the future usage or troubleshooting of GitLab should not be written directly in a forum or other messaging system, @@ -147,11 +145,11 @@ If you have questions when considering, authoring, or editing documentation, ask the Technical Writing team on Slack in `#docs` or in GitLab by mentioning the writer for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/product-categories/#devops-stages). Otherwise, forge ahead with your best effort. It does not need to be perfect; -the team is happy to review and improve upon your content. Please review the +the team is happy to review and improve upon your content. Review the [Documentation guidelines](index.md) before you begin your first documentation MR. -Having a knowledge base in any form that is separate from the documentation would -be against the documentation-first methodology because the content would overlap with +Having a knowledge base in any form that's separate from the documentation would +be against the documentation-first methodology, because the content would overlap with the documentation. ## Markdown @@ -163,17 +161,17 @@ Markdown rendering engine. For a complete Kramdown reference, see the [GitLab Markdown Kramdown Guide](https://about.gitlab.com/handbook/markdown-guide/). The [`gitlab-kramdown`](https://gitlab.com/gitlab-org/gitlab_kramdown) Ruby gem -will support all [GFM markup](../../user/markdown.md) in the future. That is, +will support all [GFM markup](../../user/markdown.md) in the future, which is all markup supported for display in the GitLab application itself. For now, use regular Markdown markup, following the rules in the linked style guide. -Note that Kramdown-specific markup (for example, `{:.class}`) will not render +Note that Kramdown-specific markup (for example, `{:.class}`) doesn't render properly on GitLab instances under [`/help`](index.md#gitlab-help). ### HTML in Markdown Hard-coded HTML is valid, although it's discouraged from being used while we -have `/help`. HTML is permitted as long as: +have `/help`. HTML is permitted if: - There's no equivalent markup in Markdown. - Advanced tables are necessary. @@ -238,14 +236,15 @@ Our goal is to have a clear hierarchical structure with meaningful URLs like `docs.gitlab.com/user/project/merge_requests/`. With this pattern, you can immediately tell that you are navigating to user-related documentation about Project features; specifically about Merge Requests. Our site's paths match -those of our repository, so the clear structure also makes documentation easier to update. +those of our repository, so the clear structure also makes documentation easier +to update. -The table below shows what kind of documentation goes where. +Put files for a specific product area into the related folder: | Directory | What belongs here | |:----------------------|:----------------------------------------------------------------------------------------------------------------------------------------| | `doc/user/` | User related documentation. Anything that can be done within the GitLab user interface goes here, including usage of the `/admin` interface. | -| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface exist under `doc/user/admin_area/`. | +| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed by using GitLab's interface exist under `doc/user/admin_area/`. | | `doc/api/` | API related documentation. | | `doc/development/` | Documentation related to the development of GitLab, whether contributing code or documentation. Related process and style guides should go here. | | `doc/legal/` | Legal documents about contributing to GitLab. | @@ -255,9 +254,11 @@ The table below shows what kind of documentation goes where. ### Work with directories and files +Refer to the following items when working with directories and files: + 1. When you create a new directory, always start with an `index.md` file. - Do not use another file name and *do not* create `README.md` files. -1. *Do not* use special characters and spaces, or capital letters in file + Don't use another file name and _do not_ create `README.md` files. +1. _Do not_ use special characters and spaces, or capital letters in file names, directory names, branch names, and anything that generates a path. 1. When creating or renaming a file or directory and it has more than one word in its name, use underscores (`_`) instead of spaces or dashes. For example, @@ -270,32 +271,32 @@ The table below shows what kind of documentation goes where. `development`. 1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`, `profile/`, `dashboard/` and `admin_area/`. - 1. `doc/user/project/` should contain all project related documentation. - 1. `doc/user/group/` should contain all group related documentation. - 1. `doc/user/profile/` should contain all profile related documentation. - Every page you would navigate under `/profile` should have its own document, - for example, `account.md`, `applications.md`, or `emails.md`. - 1. `doc/user/dashboard/` should contain all dashboard related documentation. - 1. `doc/user/admin_area/` should contain all admin related documentation - describing what can be achieved by accessing GitLab's admin interface - (_not to be confused with `doc/administration` where server access is - required_). - 1. Every category under `/admin/application_settings/` should have its - own document located at `doc/user/admin_area/settings/`. For example, - the **Visibility and Access Controls** category should have a document - located at `doc/user/admin_area/settings/visibility_and_access_controls.md`. + - `doc/user/project/` should contain all project related documentation. + - `doc/user/group/` should contain all group related documentation. + - `doc/user/profile/` should contain all profile related documentation. + Every page you would navigate under `/profile` should have its own document, + for example, `account.md`, `applications.md`, or `emails.md`. + - `doc/user/dashboard/` should contain all dashboard related documentation. + - `doc/user/admin_area/` should contain all admin related documentation + describing what can be achieved by accessing GitLab's admin interface + (_not to be confused with `doc/administration` where server access is + required_). + - Every category under `/admin/application_settings/` should have its + own document located at `doc/user/admin_area/settings/`. For example, + the **Visibility and Access Controls** category should have a document + located at `doc/user/admin_area/settings/visibility_and_access_controls.md`. 1. The `doc/topics/` directory holds topic-related technical content. Create `doc/topics/topic_name/subtopic_name/index.md` when subtopics become necessary. General user- and admin- related documentation, should be placed accordingly. 1. The `/university/` directory is *deprecated* and the majority of its documentation has been moved. -If you are unsure where to place a document or a content addition, this should -not stop you from authoring and contributing. You can use your best judgment and -then ask the reviewer of your MR to confirm your decision, and/or ask a -technical writer at any stage in the process. The technical writing team will -review all documentation changes, regardless, and can move content if there is a -better place for it. +If you're unsure where to place a document or a content addition, this shouldn't +stop you from authoring and contributing. Use your best judgment, and then ask +the reviewer of your MR to confirm your decision, or ask a technical writer at +any stage in the process. The technical writing team will review all +documentation changes, regardless, and can move content if there is a better +place for it. ### Avoid duplication @@ -350,11 +351,11 @@ Use sentence case. For example: #### UI text When referring to specific user interface text, like a button label or menu -item, use the same capitalization that is displayed in the user interface. +item, use the same capitalization that's displayed in the user interface. Standards for this content are listed in the [Pajamas Design System Content section](https://design.gitlab.com/content/punctuation/) -and typically match what is called for in this Documentation Style Guide. +and typically match what's called for in this Documentation Style Guide. -If you think there is a mistake in the way the user interface text is styled, +If you think there's a mistake in the way the user interface text is styled, create an issue or an MR to propose a change to the user interface text. #### Feature names @@ -413,8 +414,8 @@ references to user interface elements. For example: ### Inclusive language -We strive to create documentation that is inclusive. This section includes -guidance and examples in the following categories: +We strive to create documentation that's inclusive. This section includes +guidance and examples for the following categories: - [Gender-specific wording](#avoid-gender-specific-wording). (Tested in [`InclusionGender.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/InclusionGender.yml).) @@ -488,11 +489,11 @@ For more information see the following [Internet Draft specification](https://to ### Fake user information You may need to include user information in entries such as a REST call or user profile. -**Do not** use real user information or email addresses in GitLab documentation. For email +_Do not_ use real user information or email addresses in GitLab documentation. For email addresses and names, do use: -- **Email addresses**: Use an email address ending in `example.com`. -- **Names**: Use strings like `example_username`. Alternatively, use diverse or +- _Email addresses_: Use an email address ending in `example.com`. +- _Names_: Use strings like `example_username`. Alternatively, use diverse or non-gendered names with common surnames, such as `Sidney Jones`, `Zhang Wei`, or `Alex Garcia`. @@ -536,8 +537,8 @@ tenses, words, and phrases: content is accessible to more readers. - Don't write in the first person singular. (Tested in [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml).) - - Instead of "I" or "me," use "we," "you," "us," or "one." - - When possible, stay user focused by writing in the second person ("you" or + - Instead of _I_ or _me_, use _we_, _you_, _us_, or _one_. + - When possible, stay user focused by writing in the second person (_you_ or the imperative). - Don't overuse "that". In many cases, you can remove "that" from a sentence and improve readability. @@ -656,9 +657,7 @@ Some contractions, however, should be avoided: ### Punctuation -Review the general punctuation rules for the GitLab documentation in the -following table. Check specific punctuation rules for [lists](#lists) below. -Additional examples are available in the [Pajamas guide for punctuation](https://design.gitlab.com/content/punctuation/). +Follow these guidelines for punctuation: <!-- vale gitlab.Repetition = NO --> @@ -702,7 +701,7 @@ To stop the command, press <kbd>Ctrl</kbd>+<kbd>C</kbd>. ## Lists -- Always start list items with a capital letter, unless they are parameters or +- Always start list items with a capital letter, unless they're parameters or commands that are in backticks, or similar. - Always leave a blank line before and after a list. - Begin a line with spaces (not tabs) to denote a [nested sub-item](#nesting-inside-a-list-item). @@ -735,11 +734,11 @@ This is a list of available features: - Use dashes (`-`) for unordered lists instead of asterisks (`*`). - Prefix `1.` to every item in an ordered list. When rendered, the list items - will appear with sequential numbering automatically. + will appear with sequential numbering. ### Punctuation -- Do not add commas (`,`) or semicolons (`;`) to the end of list items. +- Don't add commas (`,`) or semicolons (`;`) to the ends of list items. - Only add periods to the end of a list item if the item consists of a complete sentence. The [definition of full sentence](https://www2.le.ac.uk/offices/ld/all-resources/writing/grammar/grammar-guides/sentence) is: _"a complete sentence always contains a verb, expresses a complete idea, and makes sense standing alone"_. @@ -841,8 +840,8 @@ For ordered lists, use three spaces for each level of indentation: ```` You can nest full lists inside other lists using the same rules as above. If you -want to mix types, that is also possible, as long as you don't mix items at the -same level: +want to mix types, that's also possible, if you don't mix items at the same +level: ```markdown 1. Ordered list item one. @@ -863,7 +862,7 @@ same level: Tables should be used to describe complex information in a straightforward manner. Note that in many cases, an unordered list is sufficient to describe a list of items with a single, simple description per item. But, if you have data -that is best described by a matrix, tables are the best choice for use. +that's best described by a matrix, tables are the best choice. ### Creation guidelines @@ -907,12 +906,12 @@ Valid for Markdown content only, not for front matter entries: - Quote within a quote: double quotes (`"`) wrap single quotes (`'`). Example: "I am 'quoting' something within a quote". -For other punctuation rules, please refer to the +For other punctuation rules, refer to the [GitLab UX guide](https://design.gitlab.com/content/punctuation/). ## Headings -- Add **only one H1** in each document, by adding `#` at the beginning of +- Add _only one H1_ in each document, by adding `#` at the beginning of it (when using Markdown). The `h1` will be the document `<title>`. - Start with an `h2` (`##`), and respect the order `h2` > `h3` > `h4` > `h5` > `h6`. Never skip the hierarchy level, such as `h2` > `h4` @@ -959,12 +958,13 @@ GitLab documentation from both the GitLab application and external sites. ### Anchor links -Headings generate anchor links automatically when rendered. `## This is an example` -generates the anchor `#this-is-an-example`. +Headings generate anchor links when rendered. `## This is an example` generates +the anchor `#this-is-an-example`. NOTE: **Note:** -[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39717) in GitLab 13.4, [product badges](#product-badges) used in headings aren't included in the -generated anchor links. For example, when you link to +[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39717) in +GitLab 13.4, [product badges](#product-badges) used in headings aren't included +in the generated anchor links. For example, when you link to `## This is an example **(CORE)**`, use the anchor `#this-is-an-example`. Keep in mind that the GitLab user interface links to many documentation pages @@ -985,7 +985,7 @@ Important: - Do not link to `h1` headings. Note that, with Kramdown, it is possible to add a custom ID to an HTML element -with Markdown markup, but they **do not** work in GitLab's `/help`. Therefore, +with Markdown markup, but they _do not_ work in GitLab's `/help`. Therefore, do not use this option until further notice. ## Links @@ -1012,7 +1012,7 @@ We include guidance for links in the following categories: ### Basic link criteria - Use inline link Markdown markup `[Text](https://example.com)`. - It's easier to read, review, and maintain. *Do not* use `[Text][identifier]`. + It's easier to read, review, and maintain. _Do not_ use `[Text][identifier]`. - Use [meaningful anchor texts](https://www.futurehosting.com/blog/links-should-have-meaningful-anchor-text-heres-why/). For example, instead of writing something like `Read more about GitLab Issue Boards [here](LINK)`, @@ -1133,7 +1133,7 @@ Instead: - Contained in a confidential issue. - Requires special permission to a project to view. - Provide a link in back ticks (`` ` ``) so that those with access to the issue - can easily navigate to it. + can navigate to it. Example: @@ -1149,8 +1149,8 @@ the commit link ensures the user lands on the line you're referring to. The **Permalink** button, which is available when viewing a file within a project, makes it easy to generate a link to the most recent commit of the given file. -- *Do:* `[link to line 3](https://gitlab.com/gitlab-org/gitlab/-/blob/11f17c56d8b7f0b752562d78a4298a3a95b5ce66/.gitlab/issue_templates/Feature%20proposal.md#L3)` -- *Don't:* `[link to line 3](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20proposal.md#L3).` +- _Do_: `[link to line 3](https://gitlab.com/gitlab-org/gitlab/-/blob/11f17c56d8b7f0b752562d78a4298a3a95b5ce66/.gitlab/issue_templates/Feature%20proposal.md#L3)` +- _Don't_: `[link to line 3](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20proposal.md#L3).` If that linked expression is no longer in that line of the file due to additional commits, you can still search the file for that query. In this case, update the @@ -1194,19 +1194,19 @@ they need to interact with the application. When you take screenshots: -- *Capture the most relevant area of the page.* Don't include unnecessary white +- _Capture the most relevant area of the page._ Don't include unnecessary white space or areas of the page that don't help illustrate the point. The left sidebar of the GitLab user interface can change, so don't include the sidebar if it's not necessary. -- *Keep it small.* If you don't need to show the full width of the screen, don't. +- _Keep it small._ If you don't need to show the full width of the screen, don't. A value of 1000 pixels is a good maximum width for your screenshot image. -- *Be consistent.* Coordinate screenshots with the other screenshots already on +- _Be consistent._ Coordinate screenshots with the other screenshots already on a documentation page. For example, if other screenshots include the left sidebar, include the sidebar in all screenshots. ### Save the image -- Save the image with a lowercase file name that is descriptive of the feature +- Save the image with a lowercase file name that's descriptive of the feature or concept in the image. If the image is of the GitLab interface, append the GitLab version to the file name, based on the following format: `image_name_vX_Y.png`. For example, for a screenshot taken from the pipelines @@ -1235,17 +1235,7 @@ documentation site. For accessibility and SEO, use [descriptions](https://webaim that: - Are accurate, succinct, and unique. -- Don't use *image of …* or *graphic of…* to describe the image. - -### Remove image shadow - -All images displayed on the [GitLab documentation site](https://docs.gitlab.com) -have a box shadow by default. To remove the box shadow, use the image class -`.image-noshadow` applied directly to an HTML `img` tag: - -```html -<img src="path/to/image.jpg" alt="Alt text (required)" class="image-noshadow"> -``` +- Don't use _image of…_ or _graphic of…_ to describe the image. ### Compress images @@ -1298,7 +1288,7 @@ for videos before reading: For an overview, see [Video Title](link-to-video). ``` -You can link any up-to-date video that is useful to the GitLab user. +You can link any up-to-date video that's useful to the GitLab user. ### Embed videos @@ -1313,11 +1303,10 @@ For videos from other sources, [link](#link-to-video) them instead. In most cases, it is better to [link to video](#link-to-video) instead, because an embed takes up a lot of space on the page and can be distracting to readers. -To embed a video, follow the instructions below and make sure you have your MR -reviewed and approved by a technical writer. +To embed a video: -1. Copy the code below and paste it into your Markdown file. Leave a blank line - above and below it. Do *not* edit the code (don't remove or add any spaces). +1. Copy the code from this procedure and paste it into your Markdown file. Leave a + blank line above and below it. Do _not_ edit the code (don't remove or add any spaces). 1. In YouTube, visit the video URL you want to display. Copy the regular URL from your browser (`https://www.youtube.com/watch?v=VIDEO-ID`) and replace the video title and link in the line under `<div class="video-fallback">`. @@ -1362,8 +1351,8 @@ the documentation site, but will be displayed on GitLab's `/help`. For example, `.gitlab-ci.yml`, `git add .`, `CODEOWNERS`, or `only: [master]`. File names, commands, entries, and anything that refers to code should be added to code blocks. To make things easier for the user, always add a full - code block for things that can be useful to copy and paste, as they can easily - do it with the button on code blocks. + code block for things that can be useful to copy and paste, as they can do it + with the button on code blocks. - HTTP methods (`HTTP POST`) and HTTP status codes, both full (`404 File Not Found`) and abbreviated (`404`), should be wrapped in inline code blocks when used in sentences. For example: Send a `DELETE` request to delete the runner. Send a `POST` request to create one. @@ -1504,8 +1493,7 @@ won't render correctly. For multiple lines, use [blockquotes](#blockquotes) instead. Alert boxes render only on the GitLab documentation site (<https://docs.gitlab.com>). -Within GitLab itself, they will appear as plain Markdown text (like the examples -above the rendered versions, below). +In the GitLab product help, alert boxes appear as plain Markdown text. These alert boxes are used in the GitLab documentation. These aren't strict guidelines, but for consistency you should try to use these values: @@ -1519,7 +1507,7 @@ guidelines, but for consistency you should try to use these values: ### Note -Notes indicate additional information that is of special use to the reader. +Notes indicate additional information that's of special use to the reader. Notes are most effective when used _sparingly_. Try to avoid them. Too many notes can impact the scannability of a topic and @@ -1625,11 +1613,11 @@ Merge requests allow you to exchange changes you made to source code and collaborate with other people on the same project. You'll see this term used in the following ways: -- Use lowercase *merge requests* regardless of whether referring to the feature +- Use lowercase _merge requests_ regardless of whether referring to the feature or individual merge requests. As noted in the GitLab [Writing Style Guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines), -if you use the **MR** acronym, expand it at least once per document page. +if you use the _MR_ acronym, expand it at least once per document page. Typically, the first use would be phrased as _merge request (MR)_ with subsequent instances being _MR_. @@ -1656,15 +1644,15 @@ elements: | Recommended | Used for | Replaces | |:--------------------|:--------------------------------------|:---------------------------| -| *select* | buttons, links, menu items, dropdowns | "click, "press," "hit" | -| *select* or *clear* | checkboxes | "enable", "click", "press" | -| *expand* | expandable sections | "open" | +| _select_ | buttons, links, menu items, dropdowns | "click, "press," "hit" | +| _select_ or _clear_ | checkboxes | "enable", "click", "press" | +| _expand_ | expandable sections | "open" | ### Other Verbs | Recommended | Used for | Replaces | |:------------|:--------------------------------|:----------------------| -| *go to* | making a browser go to location | "navigate to", "open" | +| _go to_ | making a browser go to location | "navigate to", "open" | ## GitLab versions and tiers @@ -1695,7 +1683,7 @@ following the heading for the section. To render correctly, it must be on its own line and surrounded by blank lines. - For features that need to declare the GitLab version that the feature was - introduced. Text similar to the following should be added immediately below + introduced. Text similar to the following should be added immediately following the heading as a blockquote: - `> Introduced in GitLab 11.3.`. @@ -1754,7 +1742,7 @@ If applicable, include the paid tier: `([introduced/deprecated](<link-to-issue>) Including the issue link is encouraged, but isn't a requirement. For example: ```markdown -The voting strategy (introduced in GitLab 13.4) requires +The voting strategy (in GitLab 13.4 and later) requires the primary and secondary voters to agree. ``` @@ -1762,22 +1750,22 @@ the primary and secondary voters to agree. When describing functionality available in past or future versions, use: -- *Earlier*, and not *older* or *before*. -- *Later*, and not *newer* or *after*. +- _Earlier_, and not _older_ or _before_. +- _Later_, and not _newer_ or _after_. For example: - Available in GitLab 12.3 and earlier. - Available in GitLab 12.4 and later. -- If using GitLab 11.4 or earlier, ... -- If using GitLab 10.6 or later, ... +- In GitLab 11.4 and earlier, ... +- In GitLab 10.6 and later, ... ### Importance of referencing GitLab versions and tiers Mentioning GitLab versions and tiers is important to all users and contributors -to quickly have access to the issue or merge request that introduced the change -for reference. Also, they can easily understand what features they have in their -GitLab instance and version, given that the note has some key information. +to quickly have access to the issue or merge request that introduced the change. +Also, they can know what features they have in their GitLab +instance and version, given that the note has some key information. `[Introduced](link-to-issue) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.7` links to the issue that introduced the feature, says which GitLab tier it @@ -1818,7 +1806,7 @@ lines with an inserted line break. Splitting product or feature names across lines makes searching for these items more difficult, and can cause problems if names change. -For example, the following Markdown content is *not* formatted correctly: +For example, the following Markdown content is _not_ formatted correctly: ```markdown When entering a product or feature name that includes a space (such as GitLab @@ -1843,14 +1831,14 @@ header or other page element according to the feature's availability: | GitLab Starter and GitLab.com Bronze, and their higher tiers | `**(STARTER)**` | | GitLab Premium and GitLab.com Silver, and their higher tiers | `**(PREMIUM)**` | | GitLab Ultimate and GitLab.com Gold | `**(ULTIMATE)**` | -| *Only* GitLab Core and higher tiers (no GitLab.com-based tiers) | `**(CORE ONLY)**` | -| *Only* GitLab Starter and higher tiers (no GitLab.com-based tiers) | `**(STARTER ONLY)**` | -| *Only* GitLab Premium and higher tiers (no GitLab.com-based tiers) | `**(PREMIUM ONLY)**` | -| *Only* GitLab Ultimate (no GitLab.com-based tiers) | `**(ULTIMATE ONLY)**` | -| *Only* GitLab.com Free and higher tiers (no self-managed instances) | `**(FREE ONLY)**` | -| *Only* GitLab.com Bronze and higher tiers (no self-managed instances) | `**(BRONZE ONLY)**` | -| *Only* GitLab.com Silver and higher tiers (no self-managed instances) | `**(SILVER ONLY)**` | -| *Only* GitLab.com Gold (no self-managed instances) | `**(GOLD ONLY)**` | +| _Only_ GitLab Core and higher tiers (no GitLab.com-based tiers) | `**(CORE ONLY)**` | +| _Only_ GitLab Starter and higher tiers (no GitLab.com-based tiers) | `**(STARTER ONLY)**` | +| _Only_ GitLab Premium and higher tiers (no GitLab.com-based tiers) | `**(PREMIUM ONLY)**` | +| _Only_ GitLab Ultimate (no GitLab.com-based tiers) | `**(ULTIMATE ONLY)**` | +| _Only_ GitLab.com Free and higher tiers (no self-managed instances) | `**(FREE ONLY)**` | +| _Only_ GitLab.com Bronze and higher tiers (no self-managed instances) | `**(BRONZE ONLY)**` | +| _Only_ GitLab.com Silver and higher tiers (no self-managed instances) | `**(SILVER ONLY)**` | +| _Only_ GitLab.com Gold (no self-managed instances) | `**(GOLD ONLY)**` | For clarity, all page title headers (H1s) must be have a tier markup for the lowest tier that has information on the documentation page. @@ -1877,12 +1865,12 @@ For example: Introduced by [!244](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/244), the special markup `**(STARTER)**` will generate a `span` element to trigger the badges and tooltips (`<span class="badge-trigger starter">`). When the keyword -*only* is added, the corresponding GitLab.com badge will not be displayed. +_only_ is added, the corresponding GitLab.com badge will not be displayed. ## Specific sections Certain styles should be applied to specific sections. Styles for specific -sections are outlined below. +sections are outlined in this section. ### GitLab restart @@ -1915,17 +1903,16 @@ of the Ruby website). GitLab currently officially supports two installation methods: installations from source and Omnibus packages installations. -Whenever there is a setting that is configurable for both installation methods, -prefer to document it in the CE documentation to avoid duplication. +Whenever there's a setting that's configurable for both installation methods, +the preference is to document it in the CE documentation to avoid duplication. Configuration settings include: -1. Settings that touch configuration files in `config/`. -1. NGINX settings and settings in `lib/support/` in general. +- Settings that touch configuration files in `config/`. +- NGINX settings and settings in `lib/support/` in general. -When there is a list of steps to perform, usually that entails editing the -configuration file and reconfiguring/restarting GitLab. In such case, follow -the style below as a guide: +When you document a list of steps, it may entail editing the configuration file +and reconfiguring or restarting GitLab. In that case, use these styles: ````markdown **For Omnibus installations** @@ -1977,8 +1964,8 @@ can facilitate this by making sure the troubleshooting content addresses: If the contents of each category can be summarized in one line and a list of steps aren't required, consider setting up a [table](#tables) with headers of -*Problem* \| *Cause* \| *Solution* (or *Workaround* if the fix is temporary), or -*Error message* \| *Solution*. +_Problem_ \| _Cause_ \| _Solution_ (or _Workaround_ if the fix is temporary), or +_Error message_ \| _Solution_. ## Feature flags diff --git a/doc/development/geo/framework.md b/doc/development/geo/framework.md index 55f4be07bb4..79e9868ec85 100644 --- a/doc/development/geo/framework.md +++ b/doc/development/geo/framework.md @@ -207,6 +207,11 @@ For example, to add support for files referenced by a `Widget` model with a end ``` + NOTE: **Note:** + + If there is a common constraint for records to be available for replication, + make sure to also overwrite the `available_replicables` scope. + 1. Create `ee/app/replicators/geo/widget_replicator.rb`. Implement the `#carrierwave_uploader` method which should return a `CarrierWave::Uploader`. And implement the class method `.model` to return the `Widget` class. @@ -560,6 +565,10 @@ Metrics are gathered by `Geo::MetricsUpdateWorker`, persisted in end ``` +1. Make sure the factory also allows setting a `project` attribute. If the model + does not have a direct relation to a project, you can use a `transient` + attribute. Check out `spec/factories/merge_request_diffs.rb` for an example. + Widget replication and verification metrics should now be available in the API, the Admin Area UI, and Prometheus! diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index ee6a83f23c8..2d81aaaf498 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -624,6 +624,13 @@ You can view more information in our runbooks such as: - Our [current log retention policies](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#retention) - A [diagram of our logging infrastructure](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#logging-infrastructure-overview) +### Job Logs + +By default, GitLab does not expire job logs. Job logs are retained indefinitely, +and can't be configured on GitLab.com to expire. You can erase job logs +[manually with the Jobs API](../../api/jobs.md#erase-a-job) or by +[deleting a pipeline](../../ci/pipelines/index.md#delete-a-pipeline). + ## GitLab.com at scale In addition to the GitLab Enterprise Edition Omnibus install, GitLab.com uses diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 4f389716f08..f5cd6991869 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -29,7 +29,7 @@ directly from a repository in GitLab. </ul> </p> </div> -<div class="col-md-3"><img src="img/ssgs_pages.png" alt="Examples of SSGs supported by Pages" class="image-noshadow middle display-block"></div> +<div class="col-md-3"><img src="img/ssgs_pages.png" alt="Examples of SSGs supported by Pages" class="middle display-block"></div> </div> To publish a website with Pages, you can use any SSG, @@ -89,7 +89,7 @@ need admin access to your domain's registrar (or control panel) to set it up wit The following diagrams show the workflows you might follow to get started with Pages. -<img src="img/new_project_for_pages_v12_5.png" alt="New projects for GitLab Pages" class="image-noshadow"> +<img src="img/new_project_for_pages_v12_5.png" alt="New projects for GitLab Pages"> ## Access to your Pages site diff --git a/lib/gitlab/database/postgres_partitioned_table.rb b/lib/gitlab/database/postgres_partitioned_table.rb new file mode 100644 index 00000000000..5856f63213a --- /dev/null +++ b/lib/gitlab/database/postgres_partitioned_table.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Gitlab + module Database + class PostgresPartitionedTable < ActiveRecord::Base + DYNAMIC_PARTITION_STRATEGIES = %w[range list].freeze + + self.primary_key = :identifier + + scope :by_identifier, ->(identifier) do + raise ArgumentError, "Table name is not fully qualified with a schema: #{identifier}" unless identifier =~ /^\w+\.\w+$/ + + find(identifier) + end + + def dynamic? + DYNAMIC_PARTITION_STRATEGIES.include?(strategy) + end + + def static? + !dynamic? + end + + def to_s + name + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 99c51cd2c5e..0e61fbeb119 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14149,15 +14149,27 @@ msgstr "" msgid "InstanceAnalytics|Canceled" msgstr "" +msgid "InstanceAnalytics|Could not load the issues and merge requests chart. Please refresh the page to try again." +msgstr "" + msgid "InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again." msgstr "" msgid "InstanceAnalytics|Failed" msgstr "" +msgid "InstanceAnalytics|Issues" +msgstr "" + +msgid "InstanceAnalytics|Issues & Merge Requests" +msgstr "" + msgid "InstanceAnalytics|Items" msgstr "" +msgid "InstanceAnalytics|Merge Requests" +msgstr "" + msgid "InstanceAnalytics|Month" msgstr "" diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb index ddace6d0533..ce369c90a9f 100644 --- a/qa/qa/page/project/settings/mirroring_repositories.rb +++ b/qa/qa/page/project/settings/mirroring_repositories.rb @@ -98,7 +98,7 @@ module QA sleep 5 refresh - wait_until(sleep_interval: 1) do + wait_until(max_duration: 180, sleep_interval: 1) do within_element_by_index(:mirrored_repository_row, row_index) do last_update = find_element(:mirror_last_update_at_cell, wait: 0) last_update.has_text?('just now') || last_update.has_text?('seconds') diff --git a/spec/frontend/analytics/instance_statistics/components/__snapshots__/pipelines_chart_spec.js.snap b/spec/frontend/analytics/instance_statistics/components/__snapshots__/instance_statistics_count_chart_spec.js.snap index 0b3b685a9f2..2e76db65a0a 100644 --- a/spec/frontend/analytics/instance_statistics/components/__snapshots__/pipelines_chart_spec.js.snap +++ b/spec/frontend/analytics/instance_statistics/components/__snapshots__/instance_statistics_count_chart_spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PipelinesChart when fetching more data when the fetchMore query returns data passes the data to the line chart 1`] = ` +exports[`InstanceStatisticsCountChart when fetching more data when the fetchMore query returns data passes the data to the line chart 1`] = ` Array [ Object { "data": Array [ @@ -90,7 +90,7 @@ Array [ ] `; -exports[`PipelinesChart with data passes the data to the line chart 1`] = ` +exports[`InstanceStatisticsCountChart with data passes the data to the line chart 1`] = ` Array [ Object { "data": Array [ diff --git a/spec/frontend/analytics/instance_statistics/components/app_spec.js b/spec/frontend/analytics/instance_statistics/components/app_spec.js index df13c9f82a9..ea232616f3d 100644 --- a/spec/frontend/analytics/instance_statistics/components/app_spec.js +++ b/spec/frontend/analytics/instance_statistics/components/app_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import InstanceStatisticsApp from '~/analytics/instance_statistics/components/app.vue'; import InstanceCounts from '~/analytics/instance_statistics/components//instance_counts.vue'; -import PipelinesChart from '~/analytics/instance_statistics/components/pipelines_chart.vue'; +import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue'; import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue'; describe('InstanceStatisticsApp', () => { @@ -24,8 +24,11 @@ describe('InstanceStatisticsApp', () => { expect(wrapper.find(InstanceCounts).exists()).toBe(true); }); - it('displays the pipelines chart component', () => { - expect(wrapper.find(PipelinesChart).exists()).toBe(true); + it('displays the instance statistics count chart component', () => { + const allCharts = wrapper.findAll(InstanceStatisticsCountChart); + expect(allCharts).toHaveLength(2); + expect(allCharts.at(0).exists()).toBe(true); + expect(allCharts.at(1).exists()).toBe(true); }); it('displays the users chart component', () => { diff --git a/spec/frontend/analytics/instance_statistics/components/pipelines_chart_spec.js b/spec/frontend/analytics/instance_statistics/components/instance_statistics_count_chart_spec.js index a06d66f783e..6efca20b67a 100644 --- a/spec/frontend/analytics/instance_statistics/components/pipelines_chart_spec.js +++ b/spec/frontend/analytics/instance_statistics/components/instance_statistics_count_chart_spec.js @@ -3,7 +3,7 @@ import { GlLineChart } from '@gitlab/ui/dist/charts'; import { GlAlert } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; import createMockApollo from 'jest/helpers/mock_apollo_helper'; -import PipelinesChart from '~/analytics/instance_statistics/components/pipelines_chart.vue'; +import InstanceStatisticsCountChart from '~/analytics/instance_statistics/components/instance_statistics_count_chart.vue'; import pipelinesStatsQuery from '~/analytics/instance_statistics/graphql/queries/pipeline_stats.query.graphql'; import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue'; import { mockCountsData1, mockCountsData2 } from '../mock_data'; @@ -12,7 +12,17 @@ import { getApolloResponse } from '../apollo_mock_data'; const localVue = createLocalVue(); localVue.use(VueApollo); -describe('PipelinesChart', () => { +const PIPELINES_KEY_TO_NAME_MAP = { + total: 'Total', + succeeded: 'Succeeded', + failed: 'Failed', + canceled: 'Canceled', + skipped: 'Skipped', +}; +const loadChartErrorMessage = 'My load error message'; +const noDataMessage = 'My no data message'; + +describe('InstanceStatisticsCountChart', () => { let wrapper; let queryHandler; @@ -21,9 +31,19 @@ describe('PipelinesChart', () => { }; const createComponent = apolloProvider => { - return shallowMount(PipelinesChart, { + return shallowMount(InstanceStatisticsCountChart, { localVue, apolloProvider, + propsData: { + keyToNameMap: PIPELINES_KEY_TO_NAME_MAP, + prefix: 'pipelines', + loadChartErrorMessage, + noDataMessage, + chartTitle: 'Foo', + yAxisTitle: 'Bar', + xAxisTitle: 'Baz', + query: pipelinesStatsQuery, + }, }); }; @@ -69,7 +89,7 @@ describe('PipelinesChart', () => { }); it('renders an no data message', () => { - expect(findAlert().text()).toBe('There is no data available.'); + expect(findAlert().text()).toBe(noDataMessage); }); it('hides the skeleton loader', () => { @@ -180,9 +200,7 @@ describe('PipelinesChart', () => { }); it('show an error message', () => { - expect(findAlert().text()).toBe( - 'Could not load the pipelines chart. Please refresh the page to try again.', - ); + expect(findAlert().text()).toBe(loadChartErrorMessage); }); }); }); diff --git a/spec/frontend/analytics/instance_statistics/utils_spec.js b/spec/frontend/analytics/instance_statistics/utils_spec.js index d480238419b..1a0cf35ead1 100644 --- a/spec/frontend/analytics/instance_statistics/utils_spec.js +++ b/spec/frontend/analytics/instance_statistics/utils_spec.js @@ -47,7 +47,21 @@ describe('getAverageByMonth', () => { describe('extractValues', () => { it('extracts only requested values', () => { const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' }; - expect(extractValues(data, ['fooBar'], 'foo', 'baz')).toEqual({ bazBar: 'quis' }); + expect(extractValues(data, ['bar'], 'foo', 'baz')).toEqual({ bazBar: 'quis' }); + }); + + it('it renames with the `renameKey` if provided', () => { + const data = { fooBar: { baz: 'quis' }, ignored: 'ignored' }; + expect(extractValues(data, ['bar'], 'foo', 'baz', { renameKey: 'renamed' })).toEqual({ + renamedBar: 'quis', + }); + }); + + it('is able to get nested data', () => { + const data = { fooBar: { even: [{ further: 'nested' }] }, ignored: 'ignored' }; + expect(extractValues(data, ['bar'], 'foo', 'even[0].further')).toEqual({ + 'even[0].furtherBar': 'nested', + }); }); it('is able to extract multiple values', () => { @@ -56,7 +70,7 @@ describe('extractValues', () => { fooBaz: { baz: 'quis' }, fooQuis: { baz: 'quis' }, }; - expect(extractValues(data, ['fooBar', 'fooBaz', 'fooQuis'], 'foo', 'baz')).toEqual({ + expect(extractValues(data, ['bar', 'baz', 'quis'], 'foo', 'baz')).toEqual({ bazBar: 'quis', bazBaz: 'quis', bazQuis: 'quis', @@ -65,7 +79,7 @@ describe('extractValues', () => { it('returns empty data set when keys are not found', () => { const data = { foo: { baz: 'quis' }, ignored: 'ignored' }; - expect(extractValues(data, ['fooBar'], 'foo', 'baz')).toEqual({}); + expect(extractValues(data, ['bar'], 'foo', 'baz')).toEqual({}); }); it('returns empty data when params are missing', () => { diff --git a/spec/frontend/sourcegraph/index_spec.js b/spec/frontend/sourcegraph/index_spec.js new file mode 100644 index 00000000000..0eccdbd3942 --- /dev/null +++ b/spec/frontend/sourcegraph/index_spec.js @@ -0,0 +1,64 @@ +import initSourcegraph from '~/sourcegraph'; + +const TEST_SOURCEGRAPH_URL = 'https://sourcegraph.test:9000'; +const TEST_GITLAB_URL = 'https://gitlab.example.com/test'; +const TEST_ASSET_HOST = 'https://gitlab-assets.example.com/'; + +describe('~/sourcegraph/index', () => { + let origGon; + + beforeEach(() => { + origGon = window.gon; + window.gon = { + sourcegraph: {}, + gitlab_url: TEST_GITLAB_URL, + }; + }); + + afterEach(() => { + document.head.innerHTML = ''; + document.body.innerHTML = ''; + window.gon = origGon; + }); + + const findScript = () => document.querySelector('script'); + + it('with no sourcegraph url, does nothing', () => { + initSourcegraph(); + + expect(findScript()).toBeNull(); + }); + + describe.each` + assetHost | assetsUrl | scriptPath + ${null} | ${`${TEST_GITLAB_URL}/assets/webpack/sourcegraph/`} | ${`${TEST_GITLAB_URL}/assets/webpack/sourcegraph/scripts/integration.bundle.js`} + ${TEST_ASSET_HOST} | ${`${TEST_ASSET_HOST}assets/webpack/sourcegraph/`} | ${`${TEST_ASSET_HOST}assets/webpack/sourcegraph/scripts/integration.bundle.js`} + `('loads sourcegraph (assetHost=$assetHost)', ({ assetHost, assetsUrl, scriptPath }) => { + beforeEach(() => { + Object.assign(window.gon, { + sourcegraph: { + url: TEST_SOURCEGRAPH_URL, + }, + asset_host: assetHost, + }); + + initSourcegraph(); + }); + + it('should add sourcegraph config constants to window', () => { + expect(window).toMatchObject({ + SOURCEGRAPH_ASSETS_URL: assetsUrl, + SOURCEGRAPH_URL: TEST_SOURCEGRAPH_URL, + SOURCEGRAPH_INTEGRATION: 'gitlab-integration', + }); + }); + + it('should add script tag', () => { + expect(findScript()).toMatchObject({ + src: scriptPath, + defer: true, + type: 'application/javascript', + }); + }); + }); +}); diff --git a/spec/lib/gitlab/database/postgres_index_spec.rb b/spec/lib/gitlab/database/postgres_index_spec.rb index 1da67a5a6c0..d65b638f7bc 100644 --- a/spec/lib/gitlab/database/postgres_index_spec.rb +++ b/spec/lib/gitlab/database/postgres_index_spec.rb @@ -3,9 +3,13 @@ require 'spec_helper' RSpec.describe Gitlab::Database::PostgresIndex do + let(:schema) { 'public' } + let(:name) { 'foo_idx' } + let(:identifier) { "#{schema}.#{name}" } + before do ActiveRecord::Base.connection.execute(<<~SQL) - CREATE INDEX foo_idx ON public.users (name); + CREATE INDEX #{name} ON public.users (name); CREATE UNIQUE INDEX bar_key ON public.users (id); CREATE TABLE example_table (id serial primary key); @@ -16,19 +20,7 @@ RSpec.describe Gitlab::Database::PostgresIndex do described_class.by_identifier(name) end - describe '.by_identifier' do - it 'finds the index' do - expect(find('public.foo_idx')).to be_a(Gitlab::Database::PostgresIndex) - end - - it 'raises an error if not found' do - expect { find('public.idontexist') }.to raise_error(ActiveRecord::RecordNotFound) - end - - it 'raises ArgumentError if given a non-fully qualified index name' do - expect { find('foo') }.to raise_error(ArgumentError, /not fully qualified/) - end - end + it_behaves_like 'a postgres model' describe '.regular' do it 'only non-unique indexes' do @@ -76,7 +68,7 @@ RSpec.describe Gitlab::Database::PostgresIndex do describe '#valid_index?' do it 'returns true if the index is invalid' do - expect(find('public.foo_idx')).to be_valid_index + expect(find(identifier)).to be_valid_index end it 'returns false if the index is marked as invalid' do @@ -86,31 +78,13 @@ RSpec.describe Gitlab::Database::PostgresIndex do WHERE pg_class.relname = 'foo_idx' AND pg_index.indexrelid = pg_class.oid SQL - expect(find('public.foo_idx')).not_to be_valid_index - end - end - - describe '#to_s' do - it 'returns the index name' do - expect(find('public.foo_idx').to_s).to eq('foo_idx') - end - end - - describe '#name' do - it 'returns the name' do - expect(find('public.foo_idx').name).to eq('foo_idx') - end - end - - describe '#schema' do - it 'returns the index schema' do - expect(find('public.foo_idx').schema).to eq('public') + expect(find(identifier)).not_to be_valid_index end end describe '#definition' do it 'returns the index definition' do - expect(find('public.foo_idx').definition).to eq('CREATE INDEX foo_idx ON public.users USING btree (name)') + expect(find(identifier).definition).to eq('CREATE INDEX foo_idx ON public.users USING btree (name)') end end end diff --git a/spec/lib/gitlab/database/postgres_partitioned_table_spec.rb b/spec/lib/gitlab/database/postgres_partitioned_table_spec.rb new file mode 100644 index 00000000000..413a4fa8842 --- /dev/null +++ b/spec/lib/gitlab/database/postgres_partitioned_table_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::PostgresPartitionedTable, type: :model do + let(:schema) { 'public' } + let(:name) { 'foo_range' } + let(:identifier) { "#{schema}.#{name}" } + + before do + ActiveRecord::Base.connection.execute(<<~SQL) + CREATE TABLE #{identifier} ( + id serial NOT NULL, + created_at timestamptz NOT NULL, + PRIMARY KEY (id, created_at) + ) PARTITION BY RANGE(created_at); + + CREATE TABLE public.foo_list ( + id serial NOT NULL, + row_type text NOT NULL, + PRIMARY KEY (id, row_type) + ) PARTITION BY LIST(row_type); + + CREATE TABLE public.foo_hash ( + id serial NOT NULL, + row_value int NOT NULL, + PRIMARY KEY (id, row_value) + ) PARTITION BY HASH (row_value); + SQL + end + + def find(identifier) + described_class.by_identifier(identifier) + end + + it_behaves_like 'a postgres model' + + describe '#dynamic?' do + it 'returns true for tables partitioned by range' do + expect(find('public.foo_range')).to be_dynamic + end + + it 'returns true for tables partitioned by list' do + expect(find('public.foo_list')).to be_dynamic + end + + it 'returns false for tables partitioned by hash' do + expect(find('public.foo_hash')).not_to be_dynamic + end + end + + describe '#static?' do + it 'returns false for tables partitioned by range' do + expect(find('public.foo_range')).not_to be_static + end + + it 'returns false for tables partitioned by list' do + expect(find('public.foo_list')).not_to be_static + end + + it 'returns true for tables partitioned by hash' do + expect(find('public.foo_hash')).to be_static + end + end + + describe '#strategy' do + it 'returns the partitioning strategy' do + expect(find(identifier).strategy).to eq('range') + end + end + + describe '#key_columns' do + it 'returns the partitioning key columns' do + expect(find(identifier).key_columns).to match_array(['created_at']) + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/postgres_model_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/postgres_model_shared_examples.rb new file mode 100644 index 00000000000..ffebbabca58 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/postgres_model_shared_examples.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a postgres model' do + describe '.by_identifier' do + it "finds the #{described_class}" do + expect(find(identifier)).to be_a(described_class) + end + + it 'raises an error if not found' do + expect { find('public.idontexist') }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'raises ArgumentError if given a non-fully qualified identifier' do + expect { find('foo') }.to raise_error(ArgumentError, /not fully qualified/) + end + end + + describe '#to_s' do + it 'returns the name' do + expect(find(identifier).to_s).to eq(name) + end + end + + describe '#schema' do + it 'returns the schema' do + expect(find(identifier).schema).to eq(schema) + end + end + + describe '#name' do + it 'returns the name' do + expect(find(identifier).name).to eq(name) + end + end +end |