...
 
Commits (517)
......@@ -865,6 +865,7 @@ gitlab:ui:visual:
tags:
- gitlab-org
before_script: []
allow_failure: true
dependencies:
- compile-assets
script:
......
......@@ -2,32 +2,10 @@
<!-- What problem do we solve? -->
### Target audience
### Intended users
<!--- For whom are we doing this? Include a [persona](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/)
listed below, if applicable, along with its [label](https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A),
or define a specific company role, e.g. "Release Manager".
Existing personas are: (copy relevant personas out of this comment, and delete any persona that does not apply)
- Parker, Product Manager, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#parker-product-manager
/label ~"Persona: Product Manager"
- Delaney, Development Team Lead, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#delaney-development-team-lead
/label ~"Persona: Development Team Lead"
- Sasha, Software Developer, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sasha-software-developer
/label ~"Persona: Software developer"
- Devon, DevOps Engineer, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#devon-devops-engineer
/label ~"Persona: DevOps Engineer"
- Sidney, Systems Administrator, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sidney-systems-administrator
/label ~"Persona: Systems Administrator"
- Sam, Security Analyst, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sam-security-analyst
/label ~"Persona: Security Analyst"
-->
<!-- Who will use this feature? If known, include any of the following: types of users (e.g. Developer), personas, or specific company roles (e.g. Release Manager). It's okay to write "Unknown" and fill this field in later.
Personas can be found at https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/ -->
### Further details
......
......@@ -8,6 +8,7 @@ require:
- rubocop-rspec
AllCops:
TargetRubyVersion: 2.5
TargetRailsVersion: 5.0
Exclude:
- 'vendor/**/*'
......@@ -187,3 +188,8 @@ Cop/InjectEnterpriseEditionModule:
Style/ReturnNil:
Enabled: true
# It isn't always safe to replace `=~` with `.match?`, especially when there are
# nil values on the left hand side
Performance/RegexpMatch:
Enabled: false
Please view this file on the master branch, on stable branches it's out of date.
## 11.8.2 (2019-03-13)
### Fixed (4 changes)
- Fix 500 error when visiting merged merge request. !9648
- Fix bridge jobs than can be hidden keys too. !9796
- Fix approval-related UI showing up in free plan. !9819
- Add 'No approvals required' view to approval rules (behind feature flag). !9899
## 11.8.0 (2019-02-22)
### Security (2 changes)
......
......@@ -2,6 +2,21 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 11.8.2 (2019-03-13)
### Security (1 change)
- Fixed ability to see private groups by users not belonging to given group.
### Fixed (5 changes)
- Fix import_jid error on project import. !25239
- Properly handle multiple X-Forwarded-For addresses in runner IP. !25511
- Fix error when viewing group issue boards when user doesn't have explicit group permissions. !25524
- Fix method to mark a project repository as writable. !25546
- Allow project members to see private group if the project is in the group namespace.
## 11.8.0 (2019-02-22)
### Security (7 changes, 1 of them is from the community)
......
......@@ -42,14 +42,14 @@ gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3'
gem 'rack-oauth2', '~> 1.2.1'
gem 'rack-oauth2', '~> 1.9.3'
gem 'jwt', '~> 2.1.0'
# Kerberos authentication. EE-only
gem 'gssapi', group: :kerberos
# Spam and anti-bot protection
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
gem 'recaptcha', '~> 4.11', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication
......@@ -275,7 +275,6 @@ gem 'addressable', '~> 2.5.2'
gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.2'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'request_store', '~> 1.3'
gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0'
......
......@@ -65,7 +65,7 @@ GEM
atomic (1.1.99)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
attr_required (1.0.0)
attr_required (1.0.1)
awesome_print (1.8.0)
aws-sdk (2.9.32)
aws-sdk-resources (= 2.9.32)
......@@ -426,7 +426,6 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jmespath (1.3.1)
jquery-atwho-rails (1.3.2)
js_regex (3.1.1)
character_set (~> 1.1)
regexp_parser (~> 1.1)
......@@ -652,12 +651,12 @@ GEM
rack-attack (4.4.1)
rack
rack-cors (1.0.2)
rack-oauth2 (1.2.3)
activesupport (>= 2.3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
multi_json (>= 1.3.6)
rack (>= 1.1)
rack-oauth2 (1.9.3)
activesupport
attr_required
httpclient
json-jwt (>= 1.9.0)
rack
rack-protection (2.0.5)
rack
rack-proxy (0.6.0)
......@@ -710,7 +709,7 @@ GEM
optimist (>= 3.0.0)
rdoc (6.0.4)
re2 (1.1.1)
recaptcha (3.0.0)
recaptcha (4.13.1)
json
recursive-open-struct (1.1.0)
redis (3.3.5)
......@@ -1083,7 +1082,6 @@ DEPENDENCIES
influxdb (~> 0.2)
jaeger-client (~> 0.10.0)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
js_regex (~> 3.1)
json-schema (~> 2.8.0)
jwt (~> 2.1.0)
......@@ -1142,7 +1140,7 @@ DEPENDENCIES
rack (= 2.0.6)
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1)
rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
rails (= 5.0.7.1)
rails-controller-testing
......@@ -1154,7 +1152,7 @@ DEPENDENCIES
rbtrace (~> 0.4)
rdoc (~> 6.0)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
recaptcha (~> 4.11)
redis (~> 3.2)
redis-namespace (~> 1.6.0)
redis-rails (~> 5.0.2)
......
<script>
import _ from 'underscore';
import helmInstallIllustration from '@gitlab/svgs/dist/illustrations/kubernetes-installation.svg';
import { GlLoadingIcon } from '@gitlab/ui';
import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png';
import gitlabLogo from 'images/cluster_app_logos/gitlab.png';
import helmLogo from 'images/cluster_app_logos/helm.png';
......@@ -23,6 +24,7 @@ export default {
applicationRow,
clipboardButton,
LoadingButton,
GlLoadingIcon,
},
props: {
type: {
......@@ -296,7 +298,12 @@ export default {
/>
</span>
</div>
<input v-else type="text" class="form-control js-endpoint" readonly value="?" />
<div v-else class="input-group">
<input type="text" class="form-control js-endpoint" readonly />
<gl-loading-icon
class="position-absolute align-self-center ml-2 js-ingress-ip-loading-icon"
/>
</div>
<p class="form-text text-muted">
{{
s__(`ClusterIntegration|Point a wildcard DNS to this
......@@ -545,13 +552,12 @@ export default {
/>
</span>
</div>
<input
v-else
type="text"
class="form-control js-knative-endpoint"
readonly
value="?"
/>
<div v-else class="input-group">
<input type="text" class="form-control js-endpoint" readonly />
<gl-loading-icon
class="position-absolute align-self-center ml-2 js-knative-ip-loading-icon"
/>
</div>
</div>
<p class="form-text text-muted col-12">
......
......@@ -3,7 +3,7 @@ import 'jquery';
// common jQuery plugins
import 'jquery-ujs';
import 'vendor/jquery.endless-scroll';
import 'vendor/jquery.caret';
import 'vendor/jquery.atwho';
import 'jquery.caret'; // must be imported before at.js
import 'at.js';
import 'vendor/jquery.scrollTo';
import 'jquery.waitforimages';
......@@ -7,6 +7,7 @@ import 'core-js/fn/array/includes';
import 'core-js/fn/object/assign';
import 'core-js/fn/object/values';
import 'core-js/fn/promise';
import 'core-js/fn/promise/finally';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
import 'core-js/fn/string/includes';
......
......@@ -33,10 +33,7 @@ export function initEmojiMap() {
}
axiosInstance
.get(
`${gon.asset_host || ''}${gon.relative_url_root ||
''}/-/emojis/${EMOJI_VERSION}/emojis.json`,
)
.get(`${gon.relative_url_root || ''}/-/emojis/${EMOJI_VERSION}/emojis.json`)
.then(({ data }) => {
emojiMap = data;
validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
......
<script>
import envrionmentsAppMixin from 'ee_else_ce/environments/mixins/environments_app_mixin';
import Flash from '../../flash';
import { s__ } from '../../locale';
import emptyState from './empty_state.vue';
......@@ -15,7 +16,7 @@ export default {
ConfirmRollbackModal,
},
mixins: [CIPaginationMixin, environmentsMixin],
mixins: [CIPaginationMixin, environmentsMixin, envrionmentsAppMixin],
props: {
endpoint: {
......@@ -42,49 +43,17 @@ export default {
type: String,
required: true,
},
// ee-only start
canaryDeploymentFeatureId: {
type: String,
required: true,
},
showCanaryDeploymentCallout: {
type: Boolean,
required: true,
},
userCalloutsPath: {
type: String,
required: true,
},
lockPromotionSvgPath: {
type: String,
required: true,
},
helpCanaryDeploymentsPath: {
type: String,
required: true,
},
// ee-only end
},
created() {
eventHub.$on('toggleFolder', this.toggleFolder);
eventHub.$on('toggleDeployBoard', this.toggleDeployBoard);
},
beforeDestroy() {
eventHub.$off('toggleFolder');
eventHub.$off('toggleDeployBoard');
},
methods: {
/**
* Toggles the visibility of the deploy boards of the clicked environment.
* @param {Object} model
*/
toggleDeployBoard(model) {
this.store.toggleDeployBoard(model.id);
},
toggleFolder(folder) {
this.store.toggleFolder(folder);
......@@ -127,9 +96,9 @@ export default {
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
<div v-if="canCreateEnvironment && !isLoading" class="nav-controls">
<a :href="newEnvironmentPath" class="btn btn-success">{{
s__('Environments|New environment')
}}</a>
<a :href="newEnvironmentPath" class="btn btn-success">
{{ s__('Environments|New environment') }}
</a>
</div>
</div>
......
import Vue from 'vue';
import canaryCalloutMixin from 'ee_else_ce/environments/mixins/canary_callout_mixin';
import environmentsFolderApp from './environments_folder_view.vue';
import { parseBoolean } from '../../lib/utils/common_utils';
import Translate from '../../vue_shared/translate';
// ee-only start
import CanaryCalloutMixin from 'ee/environments/mixins/canary_callout_mixin'; // eslint-disable-line import/order
// ee-only end
Vue.use(Translate);
export default () =>
......@@ -15,9 +12,7 @@ export default () =>
components: {
environmentsFolderApp,
},
// ee-only start
mixins: [CanaryCalloutMixin],
// ee-only end
mixins: [canaryCalloutMixin],
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
......@@ -35,13 +30,7 @@ export default () =>
folderName: this.folderName,
cssContainerClass: this.cssContainerClass,
canReadEnvironment: this.canReadEnvironment,
// ee-only start
canaryDeploymentFeatureId: this.canaryDeploymentFeatureId,
showCanaryDeploymentCallout: this.showCanaryDeploymentCallout,
userCalloutsPath: this.userCalloutsPath,
lockPromotionSvgPath: this.lockPromotionSvgPath,
helpCanaryDeploymentsPath: this.helpCanaryDeploymentsPath,
// ee-only end
...this.canaryCalloutProps,
},
});
},
......
<script>
import folderMixin from 'ee_else_ce/environments/mixins/environments_folder_view_mixin';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
......@@ -8,7 +9,7 @@ export default {
StopEnvironmentModal,
},
mixins: [environmentsMixin, CIPaginationMixin],
mixins: [environmentsMixin, CIPaginationMixin, folderMixin],
props: {
endpoint: {
......@@ -27,28 +28,6 @@ export default {
type: Boolean,
required: true,
},
// ee-only start
canaryDeploymentFeatureId: {
type: String,
required: true,
},
showCanaryDeploymentCallout: {
type: Boolean,
required: true,
},
userCalloutsPath: {
type: String,
required: true,
},
lockPromotionSvgPath: {
type: String,
required: true,
},
helpCanaryDeploymentsPath: {
type: String,
required: true,
},
// ee-only end
},
methods: {
successCallback(resp) {
......@@ -63,7 +42,8 @@ export default {
<div v-if="!isLoading" class="top-area">
<h4 class="js-folder-name environments-folder-name">
{{ s__('Environments|Environments') }} / <b>{{ folderName }}</b>
{{ s__('Environments|Environments') }} /
<b>{{ folderName }}</b>
</h4>
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
......
import Vue from 'vue';
import canaryCalloutMixin from 'ee_else_ce/environments/mixins/canary_callout_mixin';
import environmentsComponent from './components/environments_app.vue';
import { parseBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate';
// ee-only start
import CanaryCalloutMixin from 'ee/environments/mixins/canary_callout_mixin'; // eslint-disable-line import/order
// ee-only end
Vue.use(Translate);
export default () =>
......@@ -15,9 +12,7 @@ export default () =>
components: {
environmentsComponent,
},
// ee-only start
mixins: [CanaryCalloutMixin],
// ee-only end
mixins: [canaryCalloutMixin],
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
......@@ -39,13 +34,7 @@ export default () =>
cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
canReadEnvironment: this.canReadEnvironment,
// ee-only start
canaryDeploymentFeatureId: this.canaryDeploymentFeatureId,
showCanaryDeploymentCallout: this.showCanaryDeploymentCallout,
userCalloutsPath: this.userCalloutsPath,
lockPromotionSvgPath: this.lockPromotionSvgPath,
helpCanaryDeploymentsPath: this.helpCanaryDeploymentsPath,
// ee-only end
...this.canaryCalloutProps,
},
});
},
......
export default {
computed: {
canaryCalloutProps() {},
},
};
export default {
props: {
canaryDeploymentFeatureId: {
type: String,
required: false,
default: '',
},
showCanaryDeploymentCallout: {
type: Boolean,
required: false,
default: false,
},
userCalloutsPath: {
type: String,
required: false,
default: '',
},
lockPromotionSvgPath: {
type: String,
required: false,
default: '',
},
helpCanaryDeploymentsPath: {
type: String,
required: false,
default: '',
},
},
metods: {
toggleDeployBoard() {},
},
};
export default {
props: {
canaryDeploymentFeatureId: {
type: String,
required: false,
default: '',
},
showCanaryDeploymentCallout: {
type: Boolean,
required: false,
default: false,
},
userCalloutsPath: {
type: String,
required: false,
default: '',
},
lockPromotionSvgPath: {
type: String,
required: false,
default: '',
},
helpCanaryDeploymentsPath: {
type: String,
required: false,
default: '',
},
},
};
......@@ -3,13 +3,13 @@
*/
import _ from 'underscore';
import Visibility from 'visibilityjs';
import EnvironmentsStore from 'ee_else_ce/environments/stores/environments_store';
import Poll from '../../lib/utils/poll';
import { getParameterByName } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
import Flash from '../../flash';
import eventHub from '../event_hub';
import EnvironmentsStore from '../stores/environments_store';
import EnvironmentsService from '../services/environments_service';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
......
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { setDeployBoard } from 'ee_else_ce/environments/stores/helpers';
// ee-only
import { CLUSTER_TYPE } from '~/clusters/constants';
/**
* Environments Store.
*
......@@ -74,41 +73,12 @@ export default class EnvironmentsStore {
filtered = Object.assign(filtered, env);
}
if (
filtered.size === 1 &&
filtered.rollout_status &&
filtered.cluster_type !== CLUSTER_TYPE.GROUP
) {
filtered = Object.assign({}, filtered, {
hasDeployBoard: true,
isDeployBoardVisible:
oldEnvironmentState.isDeployBoardVisible === false
? oldEnvironmentState.isDeployBoardVisible
: true,
deployBoardData:
filtered.rollout_status.status === 'found' ? filtered.rollout_status : {},
isLoadingDeployBoard: filtered.rollout_status.status === 'loading',
isEmptyDeployBoard: filtered.rollout_status.status === 'not_found',
});
}
filtered = setDeployBoard(oldEnvironmentState, filtered);
return filtered;
});
this.state.environments = filteredEnvironments;
// ee-only start
/**
* Add the canary callout banner underneath the second environment listed.
*
* If there is only one environment, then add to it underneath the first.
*/
if (this.state.environments.length >= 2) {
this.state.environments[1].showCanaryCallout = true;
} else if (this.state.environments.length === 1) {
this.state.environments[0].showCanaryCallout = true;
}
// ee-only end
return filteredEnvironments;
}
......@@ -221,27 +191,4 @@ export default class EnvironmentsStore {
return environments.filter(env => env.isFolder && env.isOpen);
}
/**
* Toggles deploy board visibility for the provided environment ID.
*
* @param {Object} environment
* @return {Array}
*/
toggleDeployBoard(environmentID) {
const environments = this.state.environments.slice();
this.state.environments = environments.map(env => {
let updated = Object.assign({}, env);
if (env.id === environmentID) {
updated = Object.assign({}, updated, {
isDeployBoardVisible: !env.isDeployBoardVisible,
});
}
return updated;
});
return this.state.environments;
}
}
/**
* Deploy boards are EE only.
*
* @param {Object} environment
* @returns {Object}
*/
// eslint-disable-next-line import/prefer-default-export
export const setDeployBoard = (oldEnvironmentState, environment) => environment;
export default (fn, interval = 2000, timeout = 60000) => {
export default (fn, { interval = 2000, timeout = 60000 } = {}) => {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
const next = () => {
if (Date.now() - startTime < timeout) {
if (timeout === 0 || Date.now() - startTime < timeout) {
setTimeout(fn.bind(null, next, stop), interval);
} else {
reject(new Error('SIMPLE_POLL_TIMEOUT'));
......
......@@ -6,5 +6,5 @@ export function resetServiceWorkersPublicPath() {
// see: https://webpack.js.org/guides/public-path/
const relativeRootPath = (gon && gon.relative_url_root) || '';
const webpackAssetPath = `${relativeRootPath}/assets/webpack/`;
__webpack_public_path__ = webpackAssetPath; // eslint-disable-line camelcase
window.__webpack_public_path__ = webpackAssetPath; // eslint-disable-line
}
......@@ -11,8 +11,8 @@ import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import 'jquery.caret'; // required by at.js
import 'at.js';
import AjaxCache from '~/lib/utils/ajax_cache';
import Vue from 'vue';
import syntaxHighlight from '~/syntax_highlight';
......
......@@ -34,7 +34,7 @@ export default {
<template>
<li class="timeline-entry note note-wrapper discussion-filter-note js-discussion-filter-note">
<div class="timeline-icon">
<div class="timeline-icon d-none d-lg-flex">
<icon name="comment" />
</div>
<div class="timeline-content">
......
......@@ -87,27 +87,25 @@ export default {
<span class="note-headline-light">@{{ author.username }}</span>
</a>
<span v-else>{{ __('A deleted user') }}</span>
<span class="note-headline-light">
<span class="note-headline-meta">
<span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt">
<span class="system-note-separator">
<template v-if="actionText">{{ actionText }}</template>
</span>
<a
:href="noteTimestampLink"
class="note-timestamp system-note-separator"
@click="updateTargetNoteHash"
>
<time-ago-tooltip :time="createdAt" tooltip-placement="bottom" />
</a>
</template>
<i
class="fa fa-spinner fa-spin editing-spinner"
aria-label="Comment is being updated"
aria-hidden="true"
></i>
</span>
<span class="note-headline-light note-headline-meta">
<span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt">
<span class="system-note-separator">
<template v-if="actionText">{{ actionText }}</template>
</span>
<a
:href="noteTimestampLink"
class="note-timestamp system-note-separator"
@click="updateTargetNoteHash"
>
<time-ago-tooltip :time="createdAt" tooltip-placement="bottom" />
</a>
</template>
<i
class="fa fa-spinner fa-spin editing-spinner"
aria-label="Comment is being updated"
aria-hidden="true"
></i>
</span>
</div>
</template>
import initGroupDetails from '../shared/group_details';
document.addEventListener('DOMContentLoaded', () => {
initGroupDetails('details');
});
/* eslint-disable no-new */
import { getPagePath } from '~/lib/utils/common_utils';
import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';