diff options
author | egrieff <egrieff@gitlab.com> | 2019-05-27 09:48:03 +0100 |
---|---|---|
committer | egrieff <egrieff@gitlab.com> | 2019-05-27 09:48:03 +0100 |
commit | 96d8f009bc762b94e66fa96e5a38f3e3ee2faeb0 (patch) | |
tree | fec12a98a390b74892c8d035b4b461b9cf94e921 /app | |
parent | fd4a86884947866dff7d7165917336d094c8b276 (diff) | |
parent | 348d779beab593e0e852f5a4d285ea58db75a048 (diff) | |
download | gitlab-ce-96d8f009bc762b94e66fa96e5a38f3e3ee2faeb0.tar.gz |
Merge branch 'master' into 57825-moving-an-issue-results-in-broken-image-links-in-comments57825-moving-an-issue-results-in-broken-image-links-in-comments
Diffstat (limited to 'app')
60 files changed, 512 insertions, 270 deletions
diff --git a/app/assets/images/favicon-yellow.png b/app/assets/images/favicon-yellow.png Binary files differindex 2d5289818b4..a80827808fc 100644 --- a/app/assets/images/favicon-yellow.png +++ b/app/assets/images/favicon-yellow.png diff --git a/app/assets/javascripts/batch_comments/mixins/resolved_status.js b/app/assets/javascripts/batch_comments/mixins/resolved_status.js index 96ee9f62ba4..3bbbaa86b51 100644 --- a/app/assets/javascripts/batch_comments/mixins/resolved_status.js +++ b/app/assets/javascripts/batch_comments/mixins/resolved_status.js @@ -1,10 +1,12 @@ +import { sprintf, __ } from '~/locale'; + export default { computed: { resolveButtonTitle() { - let title = 'Mark comment as resolved'; + let title = __('Mark comment as resolved'); if (this.resolvedBy) { - title = `Resolved by ${this.resolvedBy.name}`; + title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name }); } return title; diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 561b6bdd9f1..70af333a0dd 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -1,5 +1,6 @@ import Visibility from 'visibilityjs'; import Vue from 'vue'; +import AccessorUtilities from '~/lib/utils/accessor'; import { GlToast } from '@gitlab/ui'; import PersistentUserCallout from '../persistent_user_callout'; import { s__, sprintf } from '../locale'; @@ -43,8 +44,10 @@ export default class Clusters { helpPath, ingressHelpPath, ingressDnsHelpPath, + clusterId, } = document.querySelector('.js-edit-cluster-form').dataset; + this.clusterId = clusterId; this.store = new ClustersStore(); this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath); this.store.setManagePrometheusPath(managePrometheusPath); @@ -69,6 +72,10 @@ export default class Clusters { this.errorContainer = document.querySelector('.js-cluster-error'); this.successContainer = document.querySelector('.js-cluster-success'); this.creatingContainer = document.querySelector('.js-cluster-creating'); + this.unreachableContainer = document.querySelector('.js-cluster-api-unreachable'); + this.authenticationFailureContainer = document.querySelector( + '.js-cluster-authentication-failure', + ); this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason'); this.successApplicationContainer = document.querySelector('.js-cluster-application-notice'); this.showTokenButton = document.querySelector('.js-show-cluster-token'); @@ -125,6 +132,13 @@ export default class Clusters { PersistentUserCallout.factory(callout); } + addBannerCloseHandler(el, status) { + el.querySelector('.js-close-banner').addEventListener('click', () => { + el.classList.add('hidden'); + this.setBannerDismissedState(status, true); + }); + } + addListeners() { if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken); eventHub.$on('installApplication', this.installApplication); @@ -133,6 +147,9 @@ export default class Clusters { eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data)); eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data)); eventHub.$on('uninstallApplication', data => this.uninstallApplication(data)); + // Add event listener to all the banner close buttons + this.addBannerCloseHandler(this.unreachableContainer, 'unreachable'); + this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure'); } removeListeners() { @@ -205,6 +222,8 @@ export default class Clusters { this.errorContainer.classList.add('hidden'); this.successContainer.classList.add('hidden'); this.creatingContainer.classList.add('hidden'); + this.unreachableContainer.classList.add('hidden'); + this.authenticationFailureContainer.classList.add('hidden'); } checkForNewInstalls(prevApplicationMap, newApplicationMap) { @@ -228,9 +247,32 @@ export default class Clusters { } } + setBannerDismissedState(status, isDismissed) { + if (AccessorUtilities.isLocalStorageAccessSafe()) { + window.localStorage.setItem( + `cluster_${this.clusterId}_banner_dismissed`, + `${status}_${isDismissed}`, + ); + } + } + + isBannerDismissed(status) { + let bannerState; + if (AccessorUtilities.isLocalStorageAccessSafe()) { + bannerState = window.localStorage.getItem(`cluster_${this.clusterId}_banner_dismissed`); + } + + return bannerState === `${status}_true`; + } + updateContainer(prevStatus, status, error) { this.hideAll(); + if (this.isBannerDismissed(status)) { + return; + } + this.setBannerDismissedState(status, false); + // We poll all the time but only want the `created` banner to show when newly created if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) { switch (status) { @@ -241,6 +283,12 @@ export default class Clusters { this.errorContainer.classList.remove('hidden'); this.errorReasonContainer.textContent = error; break; + case 'unreachable': + this.unreachableContainer.classList.remove('hidden'); + break; + case 'authentication_failure': + this.authenticationFailureContainer.classList.remove('hidden'); + break; case 'scheduled': case 'creating': this.creatingContainer.classList.remove('hidden'); diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js index a0ca44caa07..9216d4ab372 100644 --- a/app/assets/javascripts/commons/polyfills.js +++ b/app/assets/javascripts/commons/polyfills.js @@ -6,6 +6,7 @@ import 'core-js/fn/array/from'; import 'core-js/fn/array/includes'; import 'core-js/fn/object/assign'; import 'core-js/fn/object/values'; +import 'core-js/fn/object/entries'; import 'core-js/fn/promise'; import 'core-js/fn/promise/finally'; import 'core-js/fn/string/code-point-at'; diff --git a/app/assets/javascripts/error_tracking_settings/index.js b/app/assets/javascripts/error_tracking_settings/index.js index ce315963723..e39452353f5 100644 --- a/app/assets/javascripts/error_tracking_settings/index.js +++ b/app/assets/javascripts/error_tracking_settings/index.js @@ -1,8 +1,10 @@ import Vue from 'vue'; import ErrorTrackingSettings from './components/app.vue'; import createStore from './store'; +import initSettingsPanels from '~/settings_panels'; export default () => { + initSettingsPanels(); const formContainerEl = document.querySelector('.js-error-tracking-form'); const { dataset: { apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint }, diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js index 498c2348ca2..47e91dedd5a 100644 --- a/app/assets/javascripts/lib/graphql.js +++ b/app/assets/javascripts/lib/graphql.js @@ -3,12 +3,12 @@ import { InMemoryCache } from 'apollo-cache-inmemory'; import { createUploadLink } from 'apollo-upload-client'; import csrf from '~/lib/utils/csrf'; -export default (resolvers = {}, baseUrl = '') => { +export default (resolvers = {}, config = {}) => { let uri = `${gon.relative_url_root}/api/graphql`; - if (baseUrl) { + if (config.baseUrl) { // Prepend baseUrl and ensure that `///` are replaced with `/` - uri = `${baseUrl}${uri}`.replace(/\/{3,}/g, '/'); + uri = `${config.baseUrl}${uri}`.replace(/\/{3,}/g, '/'); } return new ApolloClient({ @@ -18,7 +18,7 @@ export default (resolvers = {}, baseUrl = '') => { [csrf.headerKey]: csrf.token, }, }), - cache: new InMemoryCache(), + cache: new InMemoryCache(config.cacheConfig), resolvers, }); }; diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 4d6327840db..624878cb5d7 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -3,7 +3,7 @@ import _ from 'underscore'; import timeago from 'timeago.js'; import dateFormat from 'dateformat'; import { pluralize } from './text_utility'; -import { languageCode, s__ } from '../../locale'; +import { languageCode, s__, __ } from '../../locale'; window.timeago = timeago; @@ -63,7 +63,15 @@ export const pad = (val, len = 2) => `0${val}`.slice(-len); * @returns {String} */ export const getDayName = date => - ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()]; + [ + __('Sunday'), + __('Monday'), + __('Tuesday'), + __('Wednesday'), + __('Thursday'), + __('Friday'), + __('Saturday'), + ][date.getDay()]; /** * @example @@ -320,13 +328,13 @@ export const getSundays = date => { } const daysToSunday = [ - 'Saturday', - 'Friday', - 'Thursday', - 'Wednesday', - 'Tuesday', - 'Monday', - 'Sunday', + __('Saturday'), + __('Friday'), + __('Thursday'), + __('Wednesday'), + __('Tuesday'), + __('Monday'), + __('Sunday'), ]; const month = date.getMonth(); @@ -336,7 +344,7 @@ export const getSundays = date => { while (dateOfMonth.getMonth() === month) { const dayName = getDayName(dateOfMonth); - if (dayName === 'Sunday') { + if (dayName === __('Sunday')) { sundays.push(new Date(dateOfMonth.getTime())); } diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index 19c4de6083d..9ddfb4bca11 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -1,4 +1,5 @@ import { BYTES_IN_KIB } from './constants'; +import { sprintf, __ } from '~/locale'; /** * Function that allows a number with an X amount of decimals @@ -72,13 +73,13 @@ export function bytesToGiB(number) { */ export function numberToHumanSize(size) { if (size < BYTES_IN_KIB) { - return `${size} bytes`; + return sprintf(__('%{size} bytes'), { size }); } else if (size < BYTES_IN_KIB * BYTES_IN_KIB) { - return `${bytesToKiB(size).toFixed(2)} KiB`; + return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(2) }); } else if (size < BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB) { - return `${bytesToMiB(size).toFixed(2)} MiB`; + return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(2) }); } - return `${bytesToGiB(size).toFixed(2)} GiB`; + return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(2) }); } /** diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index 7e8f23d6a96..5a4ff15d198 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -148,12 +148,9 @@ export default { href="#" title="Add reaction" > - <icon - css-classes="link-highlight award-control-icon-neutral" - name="emoji_slightly_smiling_face" - /> - <icon css-classes="link-highlight award-control-icon-positive" name="emoji_smiley" /> - <icon css-classes="link-highlight award-control-icon-super-positive" name="emoji_smiley" /> + <icon css-classes="link-highlight award-control-icon-neutral" name="slight-smile" /> + <icon css-classes="link-highlight award-control-icon-positive" name="smiley" /> + <icon css-classes="link-highlight award-control-icon-super-positive" name="smiley" /> </a> </div> <reply-button diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue index 17e5fcab5b7..941b6d5cab3 100644 --- a/app/assets/javascripts/notes/components/note_awards_list.vue +++ b/app/assets/javascripts/notes/components/note_awards_list.vue @@ -189,13 +189,13 @@ export default { type="button" > <span class="award-control-icon award-control-icon-neutral"> - <icon name="emoji_slightly_smiling_face" /> + <icon name="slight-smile" /> </span> <span class="award-control-icon award-control-icon-positive"> - <icon name="emoji_smiley" /> + <icon name="smiley" /> </span> <span class="award-control-icon award-control-icon-super-positive"> - <icon name="emoji_smiley" /> + <icon name="smiley" /> </span> <i aria-hidden="true" diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue index ad3d8f9329d..758f4b88be2 100644 --- a/app/assets/javascripts/repository/components/table/index.vue +++ b/app/assets/javascripts/repository/components/table/index.vue @@ -1,11 +1,15 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; +import createFlash from '~/flash'; import { sprintf, __ } from '../../../locale'; import getRefMixin from '../../mixins/get_ref'; import getFiles from '../../queries/getFiles.graphql'; +import getProjectPath from '../../queries/getProjectPath.graphql'; import TableHeader from './header.vue'; import TableRow from './row.vue'; +const PAGE_SIZE = 100; + export default { components: { GlLoadingIcon, @@ -14,14 +18,8 @@ export default { }, mixins: [getRefMixin], apollo: { - files: { - query: getFiles, - variables() { - return { - ref: this.ref, - path: this.path, - }; - }, + projectPath: { + query: getProjectPath, }, }, props: { @@ -32,7 +30,14 @@ export default { }, data() { return { - files: [], + projectPath: '', + nextPageCursor: '', + entries: { + trees: [], + submodules: [], + blobs: [], + }, + isLoadingFiles: false, }; }, computed: { @@ -42,8 +47,63 @@ export default { { path: this.path, ref: this.ref }, ); }, - isLoadingFiles() { - return this.$apollo.queries.files.loading; + }, + watch: { + $route: function routeChange() { + this.entries.trees = []; + this.entries.submodules = []; + this.entries.blobs = []; + this.nextPageCursor = ''; + this.fetchFiles(); + }, + }, + mounted() { + // We need to wait for `ref` and `projectPath` to be set + this.$nextTick(() => this.fetchFiles()); + }, + methods: { + fetchFiles() { + this.isLoadingFiles = true; + + return this.$apollo + .query({ + query: getFiles, + variables: { + projectPath: this.projectPath, + ref: this.ref, + path: this.path, + nextPageCursor: this.nextPageCursor, + pageSize: PAGE_SIZE, + }, + }) + .then(({ data }) => { + if (!data) return; + + const pageInfo = this.hasNextPage(data.project.repository.tree); + + this.isLoadingFiles = false; + this.entries = Object.keys(this.entries).reduce( + (acc, key) => ({ + ...acc, + [key]: this.normalizeData(key, data.project.repository.tree[key].edges), + }), + {}, + ); + + if (pageInfo && pageInfo.hasNextPage) { + this.nextPageCursor = pageInfo.endCursor; + this.fetchFiles(); + } + }) + .catch(() => createFlash(__('An error occurding while fetching folder content.'))); + }, + normalizeData(key, data) { + return this.entries[key].concat(data.map(({ node }) => node)); + }, + hasNextPage(data) { + return [] + .concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo) + .find(({ hasNextPage }) => hasNextPage); }, }, }; @@ -58,18 +118,21 @@ export default { tableCaption }} </caption> - <table-header /> + <table-header v-once /> <tbody> - <table-row - v-for="entry in files" - :id="entry.id" - :key="entry.id" - :path="entry.flatPath" - :type="entry.type" - /> + <template v-for="val in entries"> + <table-row + v-for="entry in val" + :id="entry.id" + :key="`${entry.flatPath}-${entry.id}`" + :current-path="path" + :path="entry.flatPath" + :type="entry.type" + /> + </template> </tbody> </table> - <gl-loading-icon v-if="isLoadingFiles" class="my-3" size="md" /> + <gl-loading-icon v-show="isLoadingFiles" class="my-3" size="md" /> </div> </div> </template> diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index 0ad0fdbd605..9a264bef87e 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -6,7 +6,11 @@ export default { mixins: [getRefMixin], props: { id: { - type: Number, + type: String, + required: true, + }, + currentPath: { + type: String, required: true, }, path: { @@ -26,7 +30,7 @@ export default { return `fa-${getIconName(this.type, this.path)}`; }, isFolder() { - return this.type === 'folder'; + return this.type === 'tree'; }, isSubmodule() { return this.type === 'commit'; @@ -34,6 +38,12 @@ export default { linkComponent() { return this.isFolder ? 'router-link' : 'a'; }, + fullPath() { + return this.path.replace(new RegExp(`^${this.currentPath}/`), ''); + }, + shortSha() { + return this.id.slice(0, 8); + }, }, methods: { openRow() { @@ -49,9 +59,11 @@ export default { <tr v-once :class="`file_${id}`" class="tree-item" @click="openRow"> <td class="tree-item-file-name"> <i :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i> - <component :is="linkComponent" :to="routerLinkTo" class="str-truncated">{{ path }}</component> + <component :is="linkComponent" :to="routerLinkTo" class="str-truncated"> + {{ fullPath }} + </component> <template v-if="isSubmodule"> - @ <a href="#" class="commit-sha">{{ id }}</a> + @ <a href="#" class="commit-sha">{{ shortSha }}</a> </template> </td> <td class="d-none d-sm-table-cell tree-commit"></td> diff --git a/app/assets/javascripts/repository/fragmentTypes.json b/app/assets/javascripts/repository/fragmentTypes.json new file mode 100644 index 00000000000..949ebca432b --- /dev/null +++ b/app/assets/javascripts/repository/fragmentTypes.json @@ -0,0 +1 @@ +{"__schema":{"types":[{"kind":"INTERFACE","name":"Entry","possibleTypes":[{"name":"Blob"},{"name":"Submodule"},{"name":"TreeEntry"}]}]}} diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js index c85db5c01e5..c64d16ef02a 100644 --- a/app/assets/javascripts/repository/graphql.js +++ b/app/assets/javascripts/repository/graphql.js @@ -1,45 +1,42 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import createDefaultClient from '~/lib/graphql'; +import introspectionQueryResultData from './fragmentTypes.json'; Vue.use(VueApollo); -const defaultClient = createDefaultClient({ - Query: { - files() { - return [ - { - __typename: 'file', - id: 1, - name: 'app', - flatPath: 'app', - type: 'folder', - }, - { - __typename: 'file', - id: 2, - name: 'gitlab-svg', - flatPath: 'gitlab-svg', - type: 'commit', - }, - { - __typename: 'file', - id: 3, - name: 'index.js', - flatPath: 'index.js', - type: 'blob', - }, - { - __typename: 'file', - id: 4, - name: 'test.pdf', - flatPath: 'fixtures/test.pdf', - type: 'blob', - }, - ]; +// We create a fragment matcher so that we can create a fragment from an interface +// Without this, Apollo throws a heuristic fragment matcher warning +const fragmentMatcher = new IntrospectionFragmentMatcher({ + introspectionQueryResultData, +}); + +const defaultClient = createDefaultClient( + {}, + { + cacheConfig: { + fragmentMatcher, + dataIdFromObject: obj => { + // eslint-disable-next-line no-underscore-dangle + switch (obj.__typename) { + // We need to create a dynamic ID for each entry + // Each entry can have the same ID as the ID is a commit ID + // So we create a unique cache ID with the path and the ID + case 'TreeEntry': + case 'Submodule': + case 'Blob': + return `${obj.flatPath}-${obj.id}`; + default: + // If the type doesn't match any of the above we fallback + // to using the default Apollo ID + // eslint-disable-next-line no-underscore-dangle + return obj.id || obj._id; + } + }, }, }, -}); +); export default new VueApollo({ defaultClient, diff --git a/app/assets/javascripts/repository/queries/getFiles.graphql b/app/assets/javascripts/repository/queries/getFiles.graphql index fb446780ed1..a9b61d28560 100644 --- a/app/assets/javascripts/repository/queries/getFiles.graphql +++ b/app/assets/javascripts/repository/queries/getFiles.graphql @@ -1,7 +1,55 @@ -query getFiles($path: String!, $ref: String!) { - files(path: $path, ref: $ref) @client { - id - flatPath - type +fragment TreeEntry on Entry { + id + flatPath + type +} + +fragment PageInfo on PageInfo { + hasNextPage + endCursor +} + +query getFiles( + $projectPath: ID! + $path: String + $ref: String! + $pageSize: Int! + $nextPageCursor: String +) { + project(fullPath: $projectPath) { + repository { + tree(path: $path, ref: $ref) { + trees(first: $pageSize, after: $nextPageCursor) { + edges { + node { + ...TreeEntry + } + } + pageInfo { + ...PageInfo + } + } + submodules(first: $pageSize, after: $nextPageCursor) { + edges { + node { + ...TreeEntry + } + } + pageInfo { + ...PageInfo + } + } + blobs(first: $pageSize, after: $nextPageCursor) { + edges { + node { + ...TreeEntry + } + } + pageInfo { + ...PageInfo + } + } + } + } } } diff --git a/app/assets/javascripts/repository/queries/getProjectPath.graphql b/app/assets/javascripts/repository/queries/getProjectPath.graphql new file mode 100644 index 00000000000..74e73e07577 --- /dev/null +++ b/app/assets/javascripts/repository/queries/getProjectPath.graphql @@ -0,0 +1,3 @@ +query getProjectPath { + projectPath +} diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js index b42a96a4ee2..f7132b99d9e 100644 --- a/app/assets/javascripts/repository/router.js +++ b/app/assets/javascripts/repository/router.js @@ -12,16 +12,11 @@ export default function createRouter(base, baseRef) { base: joinPaths(gon.relative_url_root || '', base), routes: [ { - path: '/', - name: 'projectRoot', - component: IndexPage, - }, - { path: `/tree/${baseRef}(/.*)?`, name: 'treePath', component: TreePage, props: route => ({ - path: route.params.pathMatch, + path: route.params.pathMatch.replace(/^\//, ''), }), beforeEnter(to, from, next) { document @@ -31,6 +26,11 @@ export default function createRouter(base, baseRef) { next(); }, }, + { + path: '/', + name: 'projectRoot', + component: IndexPage, + }, ], }); } diff --git a/app/assets/javascripts/repository/utils/icon.js b/app/assets/javascripts/repository/utils/icon.js index 3e93ff0ec39..661ebb6edfc 100644 --- a/app/assets/javascripts/repository/utils/icon.js +++ b/app/assets/javascripts/repository/utils/icon.js @@ -1,5 +1,5 @@ const entryTypeIcons = { - folder: 'folder', + tree: 'folder', commit: 'archive', }; diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue index 10e2c8453e2..35eba266625 100644 --- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -194,9 +194,9 @@ export default { v-show="noEmoji" class="js-no-emoji-placeholder no-emoji-placeholder position-relative" > - <icon name="emoji_slightly_smiling_face" css-classes="award-control-icon-neutral" /> - <icon name="emoji_smiley" css-classes="award-control-icon-positive" /> - <icon name="emoji_smile" css-classes="award-control-icon-super-positive" /> + <icon name="slight-smile" css-classes="award-control-icon-neutral" /> + <icon name="smiley" css-classes="award-control-icon-positive" /> + <icon name="smile" css-classes="award-control-icon-super-positive" /> </span> </button> </span> diff --git a/app/assets/javascripts/usage_ping_consent.js b/app/assets/javascripts/usage_ping_consent.js index d3d745a3c11..1e7a5fb19c2 100644 --- a/app/assets/javascripts/usage_ping_consent.js +++ b/app/assets/javascripts/usage_ping_consent.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import axios from './lib/utils/axios_utils'; import Flash, { hideFlash } from './flash'; import { parseBoolean } from './lib/utils/common_utils'; +import { __ } from './locale'; export default () => { $('body').on('click', '.js-usage-consent-action', e => { @@ -25,7 +26,7 @@ export default () => { }) .catch(() => { hideConsentMessage(); - Flash('Something went wrong. Try again later.'); + Flash(__('Something went wrong. Try again later.')); }); }); }; diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 8c71615dff2..7e6f02b10af 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -5,7 +5,7 @@ import $ from 'jquery'; import _ from 'underscore'; import axios from './lib/utils/axios_utils'; -import { __ } from './locale'; +import { s__, __, sprintf } from './locale'; import ModalStore from './boards/stores/modal_store'; // TODO: remove eventHub hack after code splitting refactor @@ -157,14 +157,20 @@ function UsersSelect(currentUser, els, options = {}) { .get(0); if (selectedUsers.length === 0) { - return 'Unassigned'; + return s__('UsersSelect|Unassigned'); } else if (selectedUsers.length === 1) { return firstUser.name; } else if (isSelected) { const otherSelected = selectedUsers.filter(s => s !== selectedUser.id); - return `${selectedUser.name} + ${otherSelected.length} more`; + return sprintf(s__('UsersSelect|%{name} + %{length} more'), { + name: selectedUser.name, + length: otherSelected.length, + }); } else { - return `${firstUser.name} + ${selectedUsers.length - 1} more`; + return sprintf(s__('UsersSelect|%{name} + %{length} more'), { + name: firstUser.name, + length: selectedUsers.length - 1, + }); } }; @@ -218,11 +224,11 @@ function UsersSelect(currentUser, els, options = {}) { tooltipTitle = _.escape(user.name); } else { user = { - name: 'Unassigned', + name: s__('UsersSelect|Unassigned'), username: '', avatar: '', }; - tooltipTitle = __('Assignee'); + tooltipTitle = s__('UsersSelect|Assignee'); } $value.html(assigneeTemplate(user)); $collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle'); @@ -233,7 +239,11 @@ function UsersSelect(currentUser, els, options = {}) { '<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>', ); assigneeTemplate = _.template( - '<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>', + `<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> + ${sprintf(s__('UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}'), { + openingTag: '<a href="#" class="js-assign-yourself">', + closingTag: '</a>', + })}</span> <% } %>`, ); return $dropdown.glDropdown({ showMenuAbove: showMenuAbove, @@ -302,7 +312,7 @@ function UsersSelect(currentUser, els, options = {}) { showDivider += 1; users.unshift({ beforeDivider: true, - name: 'Unassigned', + name: s__('UsersSelect|Unassigned'), id: 0, }); } @@ -310,7 +320,7 @@ function UsersSelect(currentUser, els, options = {}) { showDivider += 1; name = showAnyUser; if (name === true) { - name = 'Any User'; + name = s__('UsersSelect|Any User'); } anyUser = { beforeDivider: true, @@ -596,7 +606,7 @@ function UsersSelect(currentUser, els, options = {}) { showEmailUser = $(select).data('emailUser'); firstUser = $(select).data('firstUser'); return $(select).select2({ - placeholder: 'Search for a user', + placeholder: __('Search for a user'), multiple: $(select).hasClass('multiselect'), minimumInputLength: 0, query: function(query) { @@ -621,7 +631,7 @@ function UsersSelect(currentUser, els, options = {}) { } if (showNullUser) { nullUser = { - name: 'Unassigned', + name: s__('UsersSelect|Unassigned'), id: 0, }; data.results.unshift(nullUser); @@ -629,7 +639,7 @@ function UsersSelect(currentUser, els, options = {}) { if (showAnyUser) { name = showAnyUser; if (name === true) { - name = 'Any User'; + name = s__('UsersSelect|Any User'); } anyUser = { name: name, @@ -645,7 +655,7 @@ function UsersSelect(currentUser, els, options = {}) { ) { var trimmed = query.term.trim(); emailUser = { - name: 'Invite "' + trimmed + '" by email', + name: sprintf(__('Invite "%{trimmed}" by email'), { trimmed }), username: trimmed, id: trimmed, invite: true, @@ -688,7 +698,7 @@ UsersSelect.prototype.initSelection = function(element, callback) { id = $(element).val(); if (id === '0') { nullUser = { - name: 'Unassigned', + name: s__('UsersSelect|Unassigned'), }; return callback(nullUser); } else if (id !== '') { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 8f4cae8ae58..ad0464a3a98 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -49,7 +49,7 @@ export default { required: false, default: () => ({ sourceProjectId: '', - issueId: '', + mergeRequestId: '', appUrl: '', }), }, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue index b9f5f602117..0686409a785 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue @@ -48,7 +48,7 @@ export default { visualReviewAppMeta() { return { appUrl: this.mr.appUrl, - issueId: this.mr.iid, + mergeRequestId: this.mr.iid, sourceProjectId: this.mr.sourceProjectId, }; }, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue index 780ecdcdac4..6aad2a26a53 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue @@ -14,7 +14,7 @@ export default { </script> <template> - <p v-once class="mr-info-list mr-links source-branch-removal-status append-bottom-0"> + <p v-once class="mr-info-list mr-links append-bottom-0"> <span class="status-text" v-html="removesBranchText"> </span> <i v-tooltip :title="tooltipTitle" :aria-label="tooltipTitle" class="fa fa-question-circle"> </i> diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 705ee05e29f..bf175eb5f69 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -333,41 +333,45 @@ export default { <div class="mr-widget-section"> <component :is="componentName" :mr="mr" :service="service" /> - <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links"> - {{ s__('mrWidget|Allows commits from members who can merge to the target branch') }} - </section> + <div class="mr-widget-info"> + <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links"> + <p> + {{ s__('mrWidget|Allows commits from members who can merge to the target branch') }} + </p> + </section> - <mr-widget-related-links - v-if="shouldRenderRelatedLinks" - :state="mr.state" - :related-links="mr.relatedLinks" - /> + <mr-widget-related-links + v-if="shouldRenderRelatedLinks" + :state="mr.state" + :related-links="mr.relatedLinks" + /> - <mr-widget-alert-message - v-if="showMergePipelineForkWarning" - type="warning" - :help-path="mr.mergeRequestPipelinesHelpPath" - > - {{ - s__( - 'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result', - ) - }} - </mr-widget-alert-message> + <mr-widget-alert-message + v-if="showMergePipelineForkWarning" + type="warning" + :help-path="mr.mergeRequestPipelinesHelpPath" + > + {{ + s__( + 'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result', + ) + }} + </mr-widget-alert-message> - <mr-widget-alert-message - v-if="showTargetBranchAdvancedError" - type="danger" - :help-path="mr.mergeRequestPipelinesHelpPath" - > - {{ - s__( - 'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging', - ) - }} - </mr-widget-alert-message> + <mr-widget-alert-message + v-if="showTargetBranchAdvancedError" + type="danger" + :help-path="mr.mergeRequestPipelinesHelpPath" + > + {{ + s__( + 'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging', + ) + }} + </mr-widget-alert-message> - <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" /> + <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" /> + </div> </div> <div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div> </div> diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index 4dbfd1ba6f4..a60d5eb491e 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -71,11 +71,15 @@ export default { </div> <div class="text-secondary"> <div v-if="user.bio" class="js-bio d-flex mb-1"> - <icon name="profile" css-classes="category-icon" /> + <icon name="profile" css-classes="category-icon flex-shrink-0" /> <span class="ml-1">{{ user.bio }}</span> </div> <div v-if="user.organization" class="js-organization d-flex mb-1"> - <icon v-show="!jobInfoIsLoading" name="work" css-classes="category-icon" /> + <icon + v-show="!jobInfoIsLoading" + name="work" + css-classes="category-icon flex-shrink-0" + /> <span class="ml-1">{{ user.organization }}</span> </div> <gl-skeleton-loading @@ -88,7 +92,7 @@ export default { <icon v-show="!locationIsLoading && user.location" name="location" - css-classes="category-icon" + css-classes="category-icon flex-shrink-0" /> <span class="ml-1">{{ user.location }}</span> <gl-skeleton-loading diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/components/avatar.scss index 37a729c7a63..4ab197b935b 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/components/avatar.scss @@ -1,33 +1,28 @@ -@mixin avatar-size($size, $margin-right) { - width: $size; - height: $size; - margin-right: $margin-right; -} - .avatar-circle { float: left; margin-right: 15px; border-radius: $avatar-radius; border: 1px solid $gray-normal; - &.s16 { @include avatar-size(16px, 6px); } - &.s18 { @include avatar-size(18px, 6px); } - &.s19 { @include avatar-size(19px, 6px); } - &.s20 { @include avatar-size(20px, 7px); } + &.s16 { @include avatar-size(16px, 8px); } + &.s18 { @include avatar-size(18px, 8px); } + &.s19 { @include avatar-size(19px, 8px); } + &.s20 { @include avatar-size(20px, 8px); } &.s24 { @include avatar-size(24px, 8px); } &.s26 { @include avatar-size(26px, 8px); } - &.s32 { @include avatar-size(32px, 10px); } - &.s36 { @include avatar-size(36px, 10px); } - &.s40 { @include avatar-size(40px, 10px); } - &.s46 { @include avatar-size(46px, 15px); } - &.s48 { @include avatar-size(48px, 10px); } - &.s60 { @include avatar-size(60px, 12px); } - &.s64 { @include avatar-size(64px, 14px); } - &.s70 { @include avatar-size(70px, 14px); } - &.s90 { @include avatar-size(90px, 15px); } - &.s100 { @include avatar-size(100px, 15px); } - &.s110 { @include avatar-size(110px, 15px); } - &.s140 { @include avatar-size(140px, 15px); } - &.s160 { @include avatar-size(160px, 20px); } + &.s32 { @include avatar-size(32px, 8px); } + &.s36 { @include avatar-size(36px, 16px); } + &.s40 { @include avatar-size(40px, 16px); } + &.s46 { @include avatar-size(46px, 16px); } + &.s48 { @include avatar-size(48px, 16px); } + &.s60 { @include avatar-size(60px, 16px); } + &.s64 { @include avatar-size(64px, 16px); } + &.s70 { @include avatar-size(70px, 16px); } + &.s90 { @include avatar-size(90px, 16px); } + &.s96 { @include avatar-size(96px, 16px); } + &.s100 { @include avatar-size(100px, 16px); } + &.s110 { @include avatar-size(110px, 16px); } + &.s140 { @include avatar-size(140px, 16px); } + &.s160 { @include avatar-size(160px, 16px); } } .avatar { @@ -39,6 +34,7 @@ padding: 0; background: $gray-lightest; overflow: hidden; + border-color: rgba($black, $gl-avatar-border-opacity); &.avatar-inline { float: none; @@ -64,41 +60,37 @@ &.avatar-placeholder { border: 0; } - - &:not([href]):hover { - border-color: darken($gray-normal, 10%); - } } .identicon { text-align: center; vertical-align: top; - color: $gl-gray-700; + color: $gray-800; background-color: $gray-darker; // Sizes - &.s16 { font-size: 12px; - line-height: 1.33; } + &.s16 { font-size: 10px; + line-height: 16px; } - &.s24 { font-size: 13px; - line-height: 1.8; } + &.s24 { font-size: 12px; + line-height: 24px; } &.s26 { font-size: 20px; line-height: 1.33; } - &.s32 { font-size: 20px; - line-height: 30px; } + &.s32 { font-size: 14px; + line-height: 32px; } &.s40 { font-size: 16px; line-height: 38px; } &.s48 { font-size: 20px; - line-height: 46px; } + line-height: 48px; } &.s60 { font-size: 32px; line-height: 58px; } - &.s64 { font-size: 32px; + &.s64 { font-size: 28px; line-height: 64px; } &.s70 { font-size: 34px; @@ -107,6 +99,9 @@ &.s90 { font-size: 36px; line-height: 88px; } + &.s96 { font-size: 48px; + line-height: 96px; } + &.s100 { font-size: 36px; line-height: 98px; } @@ -144,7 +139,6 @@ .avatar { border-radius: 0; - border: 0; height: auto; width: 100%; margin: 0; diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index ab9047c54e4..9b0d19b0ef0 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -8,7 +8,6 @@ @import 'framework/animations'; @import 'framework/vue_transitions'; -@import 'framework/avatar'; @import 'framework/asciidoctor'; @import 'framework/banner'; @import 'framework/blocks'; diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss index 7d5bd7a81ad..a8df7e1bfad 100644 --- a/app/assets/stylesheets/framework/awards.scss +++ b/app/assets/stylesheets/framework/awards.scss @@ -151,8 +151,7 @@ outline: 0; .award-control-icon svg { - background: $award-emoji-positive-add-bg; - fill: $award-emoji-positive-add-lines; + fill: $blue-500; } .award-control-icon-neutral { diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 53d3645cd63..17c117188b3 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -241,6 +241,7 @@ */ &.code { padding: 0; + border-radius: 0 0 $border-radius-default $border-radius-default; } .list-inline.previews { diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 5bcfd5d1322..26cbb7f5c13 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -218,7 +218,7 @@ min-width: 200px; padding-right: 25px; padding-left: 0; - height: $input-height; + height: $input-height - 2; line-height: inherit; border-color: transparent; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index d0f99c2df7e..4a9c73a1bc9 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -280,3 +280,7 @@ label { max-width: $input-lg-width; width: 100%; } + +.input-group-text { + max-height: $input-height; +} diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss index 946f575ac13..741f92110c3 100644 --- a/app/assets/stylesheets/framework/highlight.scss +++ b/app/assets/stylesheets/framework/highlight.scss @@ -8,7 +8,7 @@ pre { padding: 10px 0; border: 0; - border-radius: 0; + border-radius: 0 0 $border-radius-default $border-radius-default; font-family: $monospace-font; font-size: $code-font-size; line-height: 19px; @@ -42,6 +42,7 @@ padding: 10px; text-align: right; float: left; + border-bottom-left-radius: $border-radius-default; a { font-family: $monospace-font; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 97de0c98325..18671f7c4d8 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -376,3 +376,12 @@ } } } + +/* +* Mixin that handles the size and right margin of avatars. +*/ +@mixin avatar-size($size, $margin-right) { + width: $size; + height: $size; + margin-right: $margin-right; +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 1cf122102cc..28768bdf88f 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -589,6 +589,7 @@ $issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards */ $avatar-radius: 50%; $gl-avatar-size: 40px; +$gl-avatar-border-opacity: 0.1; /* * Blame diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss index fb4d3f23cd9..ea96381a098 100644 --- a/app/assets/stylesheets/framework/variables_overrides.scss +++ b/app/assets/stylesheets/framework/variables_overrides.scss @@ -7,6 +7,7 @@ $secondary: $gray-light; $input-disabled-bg: $gray-light; $input-border-color: $gray-200; $input-color: $gl-text-color; +$input-font-size: $gl-font-size; $font-family-sans-serif: $regular-font; $font-family-monospace: $monospace-font; $btn-line-height: 20px; diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 809ba6d4953..255383d89c8 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -69,6 +69,8 @@ align-self: flex-start; font-weight: 500; font-size: 20px; + color: $orange-900; + opacity: 1; margin: $gl-padding-8 14px 0 0; } diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index cb5f1a84005..c386493231c 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -21,7 +21,6 @@ .detail-page-header-body { position: relative; - line-height: 35px; display: flex; flex: 1 1; min-width: 0; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index b2b3720fdde..b3a634e23a3 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -494,6 +494,12 @@ table.code { } } + .line_holder:last-of-type { + td:first-child { + border-bottom-left-radius: $border-radius-default; + } + } + &.left-side-selected { td.line_content.parallel.right-side { user-select: none; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 22a515cbdaa..fd082cbe0cd 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -21,13 +21,6 @@ color: $login-brand-holder-color; } - h1:first-child { - font-weight: $gl-font-weight-normal; - margin-bottom: 0.68em; - margin-top: 0; - font-size: 34px; - } - h3 { font-size: 22px; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 44b558dd5ff..ab5a9e170f0 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -87,6 +87,11 @@ padding: $gl-padding; } +.mr-widget-info { + padding-left: $gl-padding-50 - $gl-padding-32; + padding-right: $gl-padding; +} + .mr-state-widget { color: $gl-text-color; @@ -560,6 +565,10 @@ .mr-links { padding-left: $status-icon-size + $gl-btn-padding; + + &:last-child { + padding-bottom: $gl-padding; + } } .mr-info-list { @@ -1030,11 +1039,6 @@ background: $black-transparent; } -.source-branch-removal-status { - padding-left: 50px; - padding-bottom: $gl-padding; -} - .mr-compare { .diff-file .file-title-flex-parent { top: $header-height + 51px; diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index f7ea7accab2..57a1e461b2d 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -5,8 +5,10 @@ module Clusters include Presentable include Gitlab::Utils::StrongMemoize include FromUnion + include ReactiveCaching self.table_name = 'clusters' + self.reactive_cache_key = -> (cluster) { [cluster.class.model_name.singular, cluster.id] } PROJECT_ONLY_APPLICATIONS = { Applications::Jupyter.application_name => Applications::Jupyter, @@ -57,6 +59,8 @@ module Clusters validate :no_groups, unless: :group_type? validate :no_projects, unless: :project_type? + after_save :clear_reactive_cache! + delegate :status, to: :provider, allow_nil: true delegate :status_reason, to: :provider, allow_nil: true delegate :on_creation?, to: :provider, allow_nil: true @@ -123,15 +127,19 @@ module Clusters end def status_name - if provider - provider.status_name - else - :created + provider&.status_name || connection_status.presence || :created + end + + def connection_status + with_reactive_cache do |data| + data[:connection_status] end end - def created? - status_name == :created + def calculate_reactive_cache + return unless enabled? + + { connection_status: retrieve_connection_status } end def applications @@ -204,7 +212,7 @@ module Clusters end def kube_ingress_domain - @kube_ingress_domain ||= domain.presence || instance_domain || legacy_auto_devops_domain + @kube_ingress_domain ||= domain.presence || instance_domain end def predefined_variables @@ -221,6 +229,33 @@ module Clusters @instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain end + def retrieve_connection_status + kubeclient.core_client.discover + rescue *Gitlab::Kubernetes::Errors::CONNECTION + :unreachable + rescue *Gitlab::Kubernetes::Errors::AUTHENTICATION + :authentication_failure + rescue Kubeclient::HttpError => e + kubeclient_error_status(e.message) + rescue => e + Gitlab::Sentry.track_acceptable_exception(e, extra: { cluster_id: id }) + + :unknown_failure + else + :connected + end + + # KubeClient uses the same error class + # For connection errors (eg. timeout) and + # for Kubernetes errors. + def kubeclient_error_status(message) + if message&.match?(/timed out|timeout/i) + :unreachable + else + :authentication_failure + end + end + # To keep backward compatibility with AUTO_DEVOPS_DOMAIN # environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN # is set if AUTO_DEVOPS_DOMAIN is set on any of the following options: diff --git a/app/models/identity.rb b/app/models/identity.rb index 8322b9bf35f..1cbd50205ed 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -13,6 +13,7 @@ class Identity < ApplicationRecord before_save :ensure_normalized_extern_uid, if: :extern_uid_changed? after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider? + scope :for_user, ->(user) { where(user: user) } scope :with_provider, ->(provider) { where(provider: provider) } scope :with_extern_uid, ->(provider, extern_uid) do iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider) diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index f972c40f317..90bcb3067f6 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -16,27 +16,8 @@ class ProjectAutoDevops < ApplicationRecord after_save :create_gitlab_deploy_token, if: :needs_to_create_deploy_token? - def instance_domain - Gitlab::CurrentSettings.auto_devops_domain - end - - def has_domain? - domain.present? || instance_domain.present? - end - - # From 11.8, AUTO_DEVOPS_DOMAIN has been replaced by KUBE_INGRESS_BASE_DOMAIN. - # See Clusters::Cluster#predefined_variables and https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580 - # for more info. - # - # Suppport AUTO_DEVOPS_DOMAIN is scheduled to be removed on - # https://gitlab.com/gitlab-org/gitlab-ce/issues/52363 def predefined_variables Gitlab::Ci::Variables::Collection.new.tap do |variables| - if has_domain? - variables.append(key: 'AUTO_DEVOPS_DOMAIN', - value: domain.presence || instance_domain) - end - variables.concat(deployment_strategy_default_variables) end end diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb index 33b217c8498..1634d2479a0 100644 --- a/app/presenters/clusters/cluster_presenter.rb +++ b/app/presenters/clusters/cluster_presenter.rb @@ -22,10 +22,6 @@ module Clusters "https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp? end - def can_toggle_cluster? - can?(current_user, :update_cluster, cluster) && created? - end - def can_read_cluster? can?(current_user, :read_cluster, cluster) end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index a39ff76b798..1390f7cdf46 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -25,7 +25,7 @@ module SystemNoteService text_parts = ["added #{commits_text}"] text_parts << commits_list(noteable, new_commits, existing_commits, oldrev) - text_parts << "[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" + text_parts << "[Compare with previous version](#{diff_comparison_path(noteable, project, oldrev)})" body = text_parts.join("\n\n") @@ -41,7 +41,7 @@ module SystemNoteService # # Returns the created Note object def tag_commit(noteable, project, author, tag_name) - link = url_helpers.project_tag_url(project, id: tag_name) + link = url_helpers.project_tag_path(project, id: tag_name) body = "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})" create_note(NoteSummary.new(noteable, project, author, body, action: 'tag')) @@ -272,7 +272,7 @@ module SystemNoteService text_parts = ["changed this line in"] if version_params = merge_request.version_params_for(diff_refs) line_code = change_position.line_code(project.repository) - url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code)) + url = url_helpers.diffs_project_merge_request_path(project, merge_request, version_params.merge(anchor: line_code)) text_parts << "[version #{version_index} of the diff](#{url})" else @@ -405,7 +405,7 @@ module SystemNoteService # # "created branch `201-issue-branch-button`" def new_issue_branch(issue, project, author, branch) - link = url_helpers.project_compare_url(project, from: project.default_branch, to: branch) + link = url_helpers.project_compare_path(project, from: project.default_branch, to: branch) body = "created branch [`#{branch}`](#{link}) to address this issue" @@ -668,10 +668,10 @@ module SystemNoteService @url_helpers ||= Gitlab::Routing.url_helpers end - def diff_comparison_url(merge_request, project, oldrev) + def diff_comparison_path(merge_request, project, oldrev) diff_id = merge_request.merge_request_diff.id - url_helpers.diffs_project_merge_request_url( + url_helpers.diffs_project_merge_request_path( project, merge_request, diff_id: diff_id, diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml index 8d9c083d223..60ca7e4e267 100644 --- a/app/views/award_emoji/_awards_block.html.haml +++ b/app/views/award_emoji/_awards_block.html.haml @@ -13,7 +13,7 @@ %button.btn.award-control.has-tooltip.js-add-award{ type: 'button', 'aria-label': _('Add reaction'), data: { title: _('Add reaction') } } - %span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face') - %span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley') - %span{ class: "award-control-icon award-control-icon-super-positive" }= custom_icon('emoji_smile') + %span{ class: "award-control-icon award-control-icon-neutral" }= sprite_icon('slight-smile') + %span{ class: "award-control-icon award-control-icon-positive" }= sprite_icon('smiley') + %span{ class: "award-control-icon award-control-icon-super-positive" }= sprite_icon('smile') = icon('spinner spin', class: "award-control-icon award-control-icon-loading") diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml index 89bd7b31352..ca2521e9bc6 100644 --- a/app/views/ci/variables/_variable_row.html.haml +++ b/app/views/ci/variables/_variable_row.html.haml @@ -59,7 +59,7 @@ .append-right-default = s_("CiVariable|Masked") %button{ type: 'button', - class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if is_masked}", + class: "js-project-feature-toggle project-feature-toggle qa-variable-masked #{'is-checked' if is_masked}", "aria-label": s_("CiVariable|Toggle masked") } %input{ type: "hidden", class: 'js-ci-variable-input-masked js-project-feature-toggle-input', diff --git a/app/views/clusters/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml index 160c5f009a7..a5de67be96b 100644 --- a/app/views/clusters/clusters/_banner.html.haml +++ b/app/views/clusters/clusters/_banner.html.haml @@ -5,5 +5,17 @@ .hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' } = s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...') +.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' } + .col-11 + = s_('ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct.') + .col-1.p-0 + %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×" + +.hidden.js-cluster-authentication-failure.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' } + .col-11 + = s_('ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid.') + .col-1.p-0 + %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×" + .hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' } = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details") diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml index deb6b21e2be..4dfbb310142 100644 --- a/app/views/clusters/clusters/show.html.haml +++ b/app/views/clusters/clusters/show.html.haml @@ -24,7 +24,8 @@ help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'), ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'), ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'), - manage_prometheus_path: manage_prometheus_path } } + manage_prometheus_path: manage_prometheus_path, + cluster_id: @cluster.id } } .js-cluster-application-notice .flash-container diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index f7a561afbb3..ff3410f6268 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -10,15 +10,17 @@ .container.navless-container .content = render "layouts/flash" - .row.append-bottom-15 - .col-sm-7.brand-holder - %h1 + .row.mt-3 + .col-sm-12 + %h1.mb-3.font-weight-normal = brand_title + .row.mb-3 + .col-sm-7.order-12.order-sm-1.brand-holder = brand_image - if current_appearance&.description? = brand_text - else - %h3 + %h3.mt-sm-0 = _('Open source software to collaborate on code') %p @@ -29,7 +31,7 @@ = render_if_exists 'layouts/devise_help_text' - .col-sm-5.new-session-forms-container + .col-sm-5.order-1.order-sm-12.new-session-forms-container = yield %hr.footer-fixed diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 917e7acc353..e36d5192a29 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -47,9 +47,9 @@ - if @user.status = emoji_icon @user.status.emoji %span#js-no-emoji-placeholder.no-emoji-placeholder{ class: ('hidden' if @user.status) } - = sprite_icon('emoji_slightly_smiling_face', css_class: 'award-control-icon-neutral') - = sprite_icon('emoji_smiley', css_class: 'award-control-icon-positive') - = sprite_icon('emoji_smile', css_class: 'award-control-icon-super-positive') + = sprite_icon('slight-smile', css_class: 'award-control-icon-neutral') + = sprite_icon('smiley', css_class: 'award-control-icon-positive') + = sprite_icon('smile', css_class: 'award-control-icon-super-positive') - reset_message_button = button_tag type: :button, id: 'js-clear-user-status-button', class: 'clear-user-status btn has-tooltip', diff --git a/app/views/projects/buttons/_download_links.html.haml b/app/views/projects/buttons/_download_links.html.haml index 7f2cd8e109e..d344167a6c5 100644 --- a/app/views/projects/buttons/_download_links.html.haml +++ b/app/views/projects/buttons/_download_links.html.haml @@ -1,5 +1,5 @@ - formats = [['zip', 'btn-primary'], ['tar.gz'], ['tar.bz2'], ['tar']] -.d-flex.justify-content-between +.btn-group.ml-0.w-100 - formats.each do |(fmt, extra_class)| = link_to fmt, project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: fmt), rel: 'nofollow', download: '', class: "btn btn-xs #{extra_class}" diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml index eb6838cec8d..044adb75bea 100644 --- a/app/views/projects/notes/_actions.html.haml +++ b/app/views/projects/notes/_actions.html.haml @@ -41,9 +41,9 @@ .note-actions-item = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do = icon('spinner spin') - %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') - %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') - %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile') + %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile') + %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley') + %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile') - if note_editable .note-actions-item diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml index 451a79becc3..583fc08f375 100644 --- a/app/views/projects/settings/operations/_error_tracking.html.haml +++ b/app/views/projects/settings/operations/_error_tracking.html.haml @@ -2,10 +2,12 @@ - setting = error_tracking_setting -%section.settings.expanded.no-animate +%section.settings.no-animate.js-error-tracking-settings .settings-header %h4 = _('Error Tracking') + %button.btn.js-settings-toggle{ type: 'button' } + = _('Expand') %p = _('To link Sentry to GitLab, enter your Sentry URL and Auth Token.') = link_to _('More information'), help_page_path('user/project/operations/error_tracking'), target: '_blank', rel: 'noopener noreferrer' diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml index edc2c58a8ed..0a7a155bc12 100644 --- a/app/views/projects/settings/operations/show.html.haml +++ b/app/views/projects/settings/operations/show.html.haml @@ -3,6 +3,6 @@ - breadcrumb_title _('Operations Settings') = render_if_exists 'projects/settings/operations/incidents' -= render 'projects/settings/operations/error_tracking', expanded: true += render 'projects/settings/operations/error_tracking' = render 'projects/settings/operations/external_dashboard' = render_if_exists 'projects/settings/operations/tracing' diff --git a/app/views/shared/icons/_emoji_slightly_smiling_face.svg b/app/views/shared/icons/_emoji_slightly_smiling_face.svg deleted file mode 100644 index 56dbad91554..00000000000 --- a/app/views/shared/icons/_emoji_slightly_smiling_face.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369.721.721 0 0 1 .568.047.715.715 0 0 1 .37.445c.195.625.556 1.131 1.084 1.518A2.93 2.93 0 0 0 9 12.75a2.93 2.93 0 0 0 1.775-.58 2.913 2.913 0 0 0 1.084-1.518.711.711 0 0 1 .375-.445.737.737 0 0 1 .575-.047c.195.063.34.186.433.37.094.183.11.372.047.568zM7.5 6c0 .414-.146.768-.44 1.06-.292.294-.646.44-1.06.44-.414 0-.768-.146-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06.292-.294.646-.44 1.06-.44.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06-.292.294-.646.44-1.06.44-.414 0-.768-.146-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06.292-.294.646-.44 1.06-.44.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></svg> diff --git a/app/views/shared/icons/_emoji_smile.svg b/app/views/shared/icons/_emoji_smile.svg deleted file mode 100644 index ce645fee46f..00000000000 --- a/app/views/shared/icons/_emoji_smile.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568zM14 6.37c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm-6.5 0c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm9 2.63a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></svg> diff --git a/app/views/shared/icons/_emoji_smiley.svg b/app/views/shared/icons/_emoji_smiley.svg deleted file mode 100644 index ddfae50e566..00000000000 --- a/app/views/shared/icons/_emoji_smiley.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568h.001zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6c.92.397 1.91.6 2.912.598a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39c.397-.92.6-1.91.598-2.912zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="nonzero"/></svg> diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml index 01b95145937..6e20890a47f 100644 --- a/app/views/snippets/notes/_actions.html.haml +++ b/app/views/snippets/notes/_actions.html.haml @@ -3,9 +3,9 @@ .note-actions-item = link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do = icon('spinner spin') - %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') - %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') - %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile') + %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile') + %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley') + %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile') - if note_editable .note-actions-item |