diff options
Diffstat (limited to 'app')
10 files changed, 114 insertions, 19 deletions
diff --git a/app/assets/javascripts/pages/projects/find_file/ref_switcher/index.js b/app/assets/javascripts/pages/projects/find_file/ref_switcher/index.js new file mode 100644 index 00000000000..9a3bb25de70 --- /dev/null +++ b/app/assets/javascripts/pages/projects/find_file/ref_switcher/index.js @@ -0,0 +1,38 @@ +import Vue from 'vue'; +import { s__ } from '~/locale'; +import Translate from '~/vue_shared/translate'; +import RefSelector from '~/ref/components/ref_selector.vue'; +import { visitUrl } from '~/lib/utils/url_utility'; +import { generateRefDestinationPath } from './ref_switcher_utils'; + +Vue.use(Translate); + +const REF_SWITCH_HEADER = s__('FindFile|Switch branch/tag'); + +export default () => { + const el = document.getElementById('js-blob-ref-switcher'); + if (!el) return false; + + const { projectId, ref, namespace } = el.dataset; + + return new Vue({ + el, + render(createElement) { + return createElement(RefSelector, { + props: { + projectId, + value: ref, + translations: { + dropdownHeader: REF_SWITCH_HEADER, + searchPlaceholder: REF_SWITCH_HEADER, + }, + }, + on: { + input(selected) { + visitUrl(generateRefDestinationPath(selected, namespace)); + }, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/pages/projects/find_file/ref_switcher/ref_switcher_utils.js b/app/assets/javascripts/pages/projects/find_file/ref_switcher/ref_switcher_utils.js new file mode 100644 index 00000000000..5fecd024f1a --- /dev/null +++ b/app/assets/javascripts/pages/projects/find_file/ref_switcher/ref_switcher_utils.js @@ -0,0 +1,28 @@ +import { joinPaths } from '~/lib/utils/url_utility'; + +/** + * Generates a ref destination url based on the selected ref and current url. + * @param {string} selectedRef - The selected ref from the ref dropdown. + * @param {string} namespace - The destination namespace for the path. + */ +export function generateRefDestinationPath(selectedRef, namespace) { + if (!selectedRef || !namespace) { + return window.location.href; + } + + const { pathname } = window.location; + const encodedHash = '%23'; + + const [projectRootPath] = pathname.split(namespace); + + const destinationPath = joinPaths( + projectRootPath, + namespace, + encodeURI(selectedRef).replace(/#/g, encodedHash), + ); + + const newURL = new URL(window.location); + newURL.pathname = destinationPath; + + return newURL.href; +} diff --git a/app/assets/javascripts/pages/projects/find_file/show/index.js b/app/assets/javascripts/pages/projects/find_file/show/index.js index f47888f0cb8..e207df2434b 100644 --- a/app/assets/javascripts/pages/projects/find_file/show/index.js +++ b/app/assets/javascripts/pages/projects/find_file/show/index.js @@ -1,7 +1,9 @@ import $ from 'jquery'; import ShortcutsFindFile from '~/behaviors/shortcuts/shortcuts_find_file'; import ProjectFindFile from '~/projects/project_find_file'; +import InitBlobRefSwitcher from '../ref_switcher'; +InitBlobRefSwitcher(); const findElement = document.querySelector('.js-file-finder'); const projectFindFile = new ProjectFindFile($('.file-finder-holder'), { url: findElement.dataset.fileFindUrl, diff --git a/app/assets/javascripts/work_items/components/work_item_comment_form.vue b/app/assets/javascripts/work_items/components/work_item_comment_form.vue index 9da4473cd46..a38fea01d1f 100644 --- a/app/assets/javascripts/work_items/components/work_item_comment_form.vue +++ b/app/assets/javascripts/work_items/components/work_item_comment_form.vue @@ -175,7 +175,7 @@ export default { this.isSubmitting = true; this.$emit('replying', this.commentText); - const { queryVariables, fetchByIid, sortOrder } = this; + const { queryVariables, fetchByIid } = this; try { this.track('add_work_item_comment'); @@ -193,7 +193,7 @@ export default { if (createNoteData.data?.createNote?.errors?.length) { throw new Error(createNoteData.data?.createNote?.errors[0]); } - updateCommentState(store, createNoteData, fetchByIid, queryVariables, sortOrder); + updateCommentState(store, createNoteData, fetchByIid, queryVariables); }, }); clearDraft(this.autosaveKey); diff --git a/app/assets/javascripts/work_items/components/work_item_notes.vue b/app/assets/javascripts/work_items/components/work_item_notes.vue index 474fea5cec6..457eabb5671 100644 --- a/app/assets/javascripts/work_items/components/work_item_notes.vue +++ b/app/assets/javascripts/work_items/components/work_item_notes.vue @@ -50,7 +50,6 @@ export default { }, data() { return { - notesArray: [], isLoadingMore: false, perPage: DEFAULT_PAGE_SIZE_NOTES, sortOrder: ASC, @@ -61,12 +60,12 @@ export default { initialLoading() { return this.$apollo.queries.workItemNotes.loading && !this.isLoadingMore; }, - pageInfo() { - return this.workItemNotes?.pageInfo; - }, avatarUrl() { return window.gon.current_user_avatar_url; }, + pageInfo() { + return this.workItemNotes?.pageInfo; + }, hasNextPage() { return this.pageInfo?.hasNextPage; }, @@ -95,6 +94,14 @@ export default { sortOrder: this.sortOrder, }; }, + notesArray() { + const notes = this.workItemNotes?.nodes || []; + + if (this.sortOrder === DESC) { + return [...notes].reverse(); + } + return notes; + }, }, apollo: { workItemNotes: { @@ -117,8 +124,6 @@ export default { : data.workItem?.widgets; const discussionNodes = workItemWidgets.find((widget) => widget.type === 'NOTES')?.discussions || []; - this.notesArray = discussionNodes?.nodes || []; - this.updateSortingOrderIfApplicable(); return discussionNodes; }, skip() { @@ -128,6 +133,8 @@ export default { this.$emit('error', i18n.fetchError); }, result() { + this.updateSortingOrderIfApplicable(); + if (this.hasNextPage) { this.fetchMoreNotes(); } @@ -163,7 +170,6 @@ export default { }, changeNotesSortOrder(direction) { this.sortOrder = direction; - this.notesArray = [...this.notesArray].reverse(); this.changeNotesSortOrderAfterLoading = false; }, async fetchMoreNotes() { diff --git a/app/assets/javascripts/work_items/graphql/cache_utils.js b/app/assets/javascripts/work_items/graphql/cache_utils.js index c1de9f673f2..16b892b3476 100644 --- a/app/assets/javascripts/work_items/graphql/cache_utils.js +++ b/app/assets/javascripts/work_items/graphql/cache_utils.js @@ -1,6 +1,5 @@ import { produce } from 'immer'; import { WIDGET_TYPE_NOTES } from '~/work_items/constants'; -import { ASC } from '~/notes/constants'; import { getWorkItemNotesQuery } from '~/work_items/utils'; /** @@ -12,13 +11,7 @@ import { getWorkItemNotesQuery } from '~/work_items/utils'; * @param queryVariables * @param sortOrder */ -export const updateCommentState = ( - store, - { data: { createNote } }, - fetchByIid, - queryVariables, - sortOrder, -) => { +export const updateCommentState = (store, { data: { createNote } }, fetchByIid, queryVariables) => { const notesQuery = getWorkItemNotesQuery(fetchByIid); const variables = { ...queryVariables, @@ -36,7 +29,10 @@ export const updateCommentState = ( ) : draftData.workItem.widgets.find((widget) => widget.type === WIDGET_TYPE_NOTES); - const arrayPushMethod = sortOrder === ASC ? 'push' : 'unshift'; + // as notes are currently sorted/reversed on the frontend rather than in the query + // we only ever push. + // const arrayPushMethod = sortOrder === ASC ? 'push' : 'unshift'; + const arrayPushMethod = 'push'; // manual update of cache with a completely new discussion if (createNote.note.discussion.notes.nodes.length === 1) { diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb index 35339624e37..3cab2b7aba7 100644 --- a/app/graphql/types/ci/runner_type.rb +++ b/app/graphql/types/ci/runner_type.rb @@ -14,6 +14,9 @@ module Types JOB_COUNT_LIMIT = 1000 + # Only allow ephemeral_authentication_token to be visible for a short while + RUNNER_EPHEMERAL_TOKEN_AVAILABILITY_TIME = 3.hours + alias_method :runner, :object field :access_level, ::Types::Ci::RunnerAccessLevelEnum, null: false, @@ -35,6 +38,10 @@ module Types description: 'Description of the runner.' field :edit_admin_url, GraphQL::Types::String, null: true, description: 'Admin form URL of the runner. Only available for administrators.' + field :ephemeral_authentication_token, GraphQL::Types::String, null: true, + description: 'Ephemeral authentication token used for runner machine registration.', + authorize: :read_ephemeral_token, + alpha: { milestone: '15.9' } field :executor_name, GraphQL::Types::String, null: true, description: 'Executor last advertised by the runner.', method: :executor_name @@ -134,6 +141,14 @@ module Types Gitlab::Routing.url_helpers.edit_admin_runner_url(runner) if can_admin_runners? end + def ephemeral_authentication_token + return unless runner.created_via_ui? + return unless runner.created_at > RUNNER_EPHEMERAL_TOKEN_AVAILABILITY_TIME.ago + return if runner.runner_machines.any? + + runner.token + end + def project_count BatchLoader::GraphQL.for(runner.id).batch(key: :runner_project_count) do |ids, loader, args| counts = ::Ci::Runner.project_type diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 5c4512c5f72..19acd86a7f4 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -495,6 +495,10 @@ module Ci "#{CREATED_RUNNER_TOKEN_PREFIX}#{token}" end + def created_via_ui? + token.start_with?(CREATED_RUNNER_TOKEN_PREFIX) + end + private scope :with_upgrade_status, ->(upgrade_status) do diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb index 1c23b367489..7b01dccff87 100644 --- a/app/policies/ci/runner_policy.rb +++ b/app/policies/ci/runner_policy.rb @@ -9,6 +9,10 @@ module Ci @user.owns_runner?(@subject) end + condition(:creator) do + @user == @subject.creator + end + with_options scope: :subject, score: 0 condition(:is_instance_runner) do @subject.instance_type? @@ -72,6 +76,8 @@ module Ci rule { ~admin & belongs_to_multiple_projects }.prevent :delete_runner rule { ~admin & locked }.prevent :assign_runner + + rule { creator }.enable :read_ephemeral_token end end diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml index 7cd4ab08680..1d4e907dd61 100644 --- a/app/views/projects/find_file/show.html.haml +++ b/app/views/projects/find_file/show.html.haml @@ -4,7 +4,7 @@ .file-finder-holder.tree-holder.clearfix.js-file-finder{ 'data-file-find-url': "#{escape_javascript(project_files_path(@project, @ref, format: :json))}", 'data-find-tree-url': escape_javascript(project_tree_path(@project, @ref)), 'data-blob-url-template': escape_javascript(project_blob_path(@project, @ref)) } .nav-block .tree-ref-holder - = render 'shared/ref_switcher', destination: 'find_file', path: @path + #js-blob-ref-switcher{ data: { project_id: @project.id, ref: @ref, namespace: "/-/find_file" } } %ul.breadcrumb.repo-breadcrumb %li.breadcrumb-item = link_to project_tree_path(@project, @ref) do |