Commit 273981d9 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '350457-display-environment-button' into 'master'

Blob refactor: Display environment button

See merge request gitlab-org/gitlab!78886
parents d9cad7c1 06bbebfd
......@@ -92,6 +92,8 @@ export default {
:active-viewer="viewer"
:has-render-error="hasRenderError"
:is-binary="isBinary"
:environment-name="blob.environmentFormattedExternalUrl"
:environment-path="blob.environmentExternalUrlForRouteMap"
@copy="proxyCopyRequest"
/>
</div>
......
<script>
import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import {
BTN_COPY_CONTENTS_TITLE,
BTN_DOWNLOAD_TITLE,
......@@ -37,6 +38,16 @@ export default {
required: false,
default: false,
},
environmentName: {
type: String,
required: false,
default: null,
},
environmentPath: {
type: String,
required: false,
default: null,
},
},
computed: {
downloadUrl() {
......@@ -51,6 +62,11 @@ export default {
showCopyButton() {
return !this.hasRenderError && !this.isBinary;
},
environmentTitle() {
return sprintf(s__('BlobViewer|View on %{environmentName}'), {
environmentName: this.environmentName,
});
},
},
BTN_COPY_CONTENTS_TITLE,
BTN_DOWNLOAD_TITLE,
......@@ -93,5 +109,17 @@ export default {
category="primary"
variant="default"
/>
<gl-button
v-if="environmentName && environmentPath"
v-gl-tooltip.hover
:aria-label="environmentTitle"
:title="environmentTitle"
:href="environmentPath"
data-testid="environment"
target="_blank"
icon="external-link"
category="primary"
variant="default"
/>
</gl-button-group>
</template>
......@@ -106,6 +106,8 @@ export default {
ideForkAndEditPath: '',
storedExternally: false,
externalStorage: '',
environmentFormattedExternalUrl: '',
environmentExternalUrlForRouteMap: '',
canModifyBlob: false,
canCurrentUserPushToBranch: false,
archived: false,
......
......@@ -25,6 +25,8 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
ideEditPath
forkAndEditPath
ideForkAndEditPath
environmentFormattedExternalUrl
environmentExternalUrlForRouteMap
canModifyBlob
canCurrentUserPushToBranch
archived
......
......@@ -15,8 +15,8 @@ module Environments
deployments =
if ref
Deployment.where(ref: ref.to_s)
elsif commit
Deployment.where(sha: commit.sha)
elsif sha
Deployment.where(sha: sha)
else
Deployment.none
end
......@@ -47,7 +47,7 @@ module Environments
return false unless Ability.allowed?(current_user, :read_environment, environment)
return false if ref && params[:recently_updated] && !environment.recently_updated_on_branch?(ref)
return false if ref && commit && !environment.includes_commit?(commit)
return false if ref && sha && !environment.includes_commit?(sha)
true
end
......@@ -56,8 +56,8 @@ module Environments
params[:ref].try(:to_s)
end
def commit
params[:commit]
def sha
params[:sha] || params[:commit]&.id
end
end
end
......@@ -87,6 +87,14 @@ module Types
description: 'Web path to blob permalink.',
calls_gitaly: true
field :environment_formatted_external_url, GraphQL::Types::String, null: true,
description: 'Environment on which the blob is available.',
calls_gitaly: true
field :environment_external_url_for_route_map, GraphQL::Types::String, null: true,
description: 'Web path to blob on an environment.',
calls_gitaly: true
field :code_owners, [Types::UserType], null: true,
description: 'List of code owners for the blob.',
calls_gitaly: true
......
......@@ -255,10 +255,10 @@ class Deployment < ApplicationRecord
end
end
def includes_commit?(commit)
return false unless commit
def includes_commit?(ancestor_sha)
return false unless sha
project.repository.ancestor?(commit.id, sha)
project.repository.ancestor?(ancestor_sha, sha)
end
def update_merge_request_metrics!
......
......@@ -235,10 +235,10 @@ class Environment < ApplicationRecord
self.environment_type = names.many? ? names.first : nil
end
def includes_commit?(commit)
def includes_commit?(sha)
return false unless last_deployment
last_deployment.includes_commit?(commit)
last_deployment.includes_commit?(sha)
end
def last_deployed_at
......
......@@ -79,6 +79,18 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
url_helpers.project_blob_path(project, File.join(project.repository.commit.sha, blob.path))
end
def environment_formatted_external_url
return unless environment
environment.formatted_external_url
end
def environment_external_url_for_route_map
return unless environment
environment.external_url_for(blob.path, blob.commit_id)
end
# Will be overridden in EE
def code_owners
[]
......@@ -122,6 +134,12 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
Gitlab::Routing.url_helpers
end
def environment
environment_params = project.repository.branch_exists?(blob.commit_id) ? { ref: blob.commit_id } : { sha: blob.commit_id }
environment_params[:find_latest] = true
::Environments::EnvironmentsByDeploymentsFinder.new(project, current_user, environment_params).execute.last
end
def project
blob.repository.project
end
......
......@@ -14620,6 +14620,8 @@ Returns [`Tree`](#tree).
| <a id="repositoryblobcanmodifyblob"></a>`canModifyBlob` | [`Boolean`](#boolean) | Whether the current user can modify the blob. |
| <a id="repositoryblobcodeowners"></a>`codeOwners` | [`[UserCore!]`](#usercore) | List of code owners for the blob. |
| <a id="repositoryblobeditblobpath"></a>`editBlobPath` | [`String`](#string) | Web path to edit the blob in the old-style editor. |
| <a id="repositoryblobenvironmentexternalurlforroutemap"></a>`environmentExternalUrlForRouteMap` | [`String`](#string) | Web path to blob on an environment. |
| <a id="repositoryblobenvironmentformattedexternalurl"></a>`environmentFormattedExternalUrl` | [`String`](#string) | Environment on which the blob is available. |
| <a id="repositoryblobexternalstorage"></a>`externalStorage` | [`String`](#string) | External storage being used, if enabled (for instance, 'LFS'). |
| <a id="repositoryblobexternalstorageurl"></a>`externalStorageUrl` | [`String`](#string) | Web path to download the raw blob via external storage, if enabled. |
| <a id="repositoryblobfiletype"></a>`fileType` | [`String`](#string) | Expected format of the blob based on the extension. |
......@@ -5631,6 +5631,9 @@ msgstr ""
msgid "Blame"
msgstr ""
msgid "BlobViewer|View on %{environmentName}"
msgstr ""
msgid "Block user"
msgstr ""
......
......@@ -9,7 +9,6 @@ RSpec.describe 'View on environment', :js do
let(:user) { project.creator }
before do
stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/350457
project.add_maintainer(user)
end
......
......@@ -64,6 +64,22 @@ RSpec.describe Environments::EnvironmentsByDeploymentsFinder do
end
end
context 'sha deployment' do
before do
create(:deployment, :success, environment: environment, sha: project.commit.id)
end
it 'returns environment' do
expect(described_class.new(project, user, sha: project.commit.id).execute)
.to contain_exactly(environment)
end
it 'does not return environment when sha is different' do
expect(described_class.new(project, user, sha: '1234').execute)
.to be_empty
end
end
context 'commit deployment' do
before do
create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id)
......
import { GlButtonGroup, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import BlobHeaderActions from '~/blob/components/blob_header_default_actions.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import {
BTN_COPY_CONTENTS_TITLE,
BTN_DOWNLOAD_TITLE,
BTN_RAW_TITLE,
RICH_BLOB_VIEWER,
} from '~/blob/components/constants';
import { Blob } from './mock_data';
import { Blob, mockEnvironmentName, mockEnvironmentPath } from './mock_data';
describe('Blob Header Default Actions', () => {
let wrapper;
......@@ -17,7 +17,7 @@ describe('Blob Header Default Actions', () => {
const blobHash = 'foo-bar';
function createComponent(propsData = {}) {
wrapper = mount(BlobHeaderActions, {
wrapper = shallowMountExtended(BlobHeaderActions, {
provide: {
blobHash,
},
......@@ -39,8 +39,8 @@ describe('Blob Header Default Actions', () => {
});
describe('renders', () => {
const findCopyButton = () => wrapper.find('[data-testid="copyContentsButton"]');
const findViewRawButton = () => wrapper.find('[data-testid="viewRawButton"]');
const findCopyButton = () => wrapper.findByTestId('copyContentsButton');
const findViewRawButton = () => wrapper.findByTestId('viewRawButton');
it('gl-button-group component', () => {
expect(btnGroup.exists()).toBe(true);
......@@ -89,4 +89,37 @@ describe('Blob Header Default Actions', () => {
expect(findViewRawButton().exists()).toBe(false);
});
});
describe('view on environment button', () => {
const findEnvironmentButton = () => wrapper.findByTestId('environment');
it.each`
environmentName | environmentPath | isVisible
${null} | ${null} | ${false}
${null} | ${mockEnvironmentPath} | ${false}
${mockEnvironmentName} | ${null} | ${false}
${mockEnvironmentName} | ${mockEnvironmentPath} | ${true}
`(
'when environmentName is $environmentName and environmentPath is $environmentPath',
({ environmentName, environmentPath, isVisible }) => {
createComponent({ environmentName, environmentPath });
expect(findEnvironmentButton().exists()).toBe(isVisible);
},
);
it('renders the correct attributes', () => {
createComponent({
environmentName: mockEnvironmentName,
environmentPath: mockEnvironmentPath,
});
expect(findEnvironmentButton().attributes()).toMatchObject({
title: `View on ${mockEnvironmentName}`,
href: mockEnvironmentPath,
});
expect(findEnvironmentButton().props('icon')).toBe('external-link');
});
});
});
......@@ -55,3 +55,6 @@ export const SimpleBlobContentMock = {
path: 'foo.js',
plainData: 'Plain',
};
export const mockEnvironmentName = 'my.testing.environment';
export const mockEnvironmentPath = 'https://my.testing.environment';
......@@ -11,6 +11,8 @@ export const simpleViewerMock = {
ideEditPath: 'some_file.js/ide/edit',
forkAndEditPath: 'some_file.js/fork/edit',
ideForkAndEditPath: 'some_file.js/fork/ide',
environmentFormattedExternalUrl: '',
environmentExternalUrlForRouteMap: '',
canModifyBlob: true,
canCurrentUserPushToBranch: true,
archived: false,
......
......@@ -29,6 +29,8 @@ RSpec.describe Types::Repository::BlobType do
:blame_path,
:history_path,
:permalink_path,
:environment_formatted_external_url,
:environment_external_url_for_route_map,
:code_owners,
:simple_viewer,
:rich_viewer,
......
......@@ -615,7 +615,7 @@ RSpec.describe Deployment do
it 'returns false' do
commit = project.commit('feature')
expect(deployment.includes_commit?(commit)).to be false
expect(deployment.includes_commit?(commit.id)).to be false
end
end
......@@ -623,7 +623,7 @@ RSpec.describe Deployment do
it 'returns true' do
commit = project.commit
expect(deployment.includes_commit?(commit)).to be true
expect(deployment.includes_commit?(commit.id)).to be true
end
end
......@@ -632,7 +632,7 @@ RSpec.describe Deployment do
deployment.update!(sha: Gitlab::Git::BLANK_SHA)
commit = project.commit
expect(deployment.includes_commit?(commit)).to be false
expect(deployment.includes_commit?(commit.id)).to be false
end
end
end
......
......@@ -412,7 +412,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
context 'in the same branch' do
it 'returns true' do
expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be true
expect(environment.includes_commit?(RepoHelpers.sample_commit.id)).to be true
end
end
......@@ -422,7 +422,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
end
it 'returns false' do
expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be false
expect(environment.includes_commit?(RepoHelpers.sample_commit.id)).to be false
end
end
end
......
......@@ -87,6 +87,33 @@ RSpec.describe BlobPresenter do
it { expect(presenter.permalink_path).to eq("/#{project.full_path}/-/blob/#{project.repository.commit.sha}/files/ruby/regex.rb") }
end
context 'environment has been deployed' do
let(:external_url) { "https://some.environment" }
let(:environment) { create(:environment, project: project, external_url: external_url) }
let!(:deployment) { create(:deployment, :success, environment: environment, project: project, sha: blob.commit_id) }
before do
allow(project).to receive(:public_path_for_source_path).with(blob.path, blob.commit_id).and_return(blob.path)
end
describe '#environment_formatted_external_url' do
it { expect(presenter.environment_formatted_external_url).to eq("some.environment") }
end
describe '#environment_external_url_for_route_map' do
it { expect(presenter.environment_external_url_for_route_map).to eq("#{external_url}/#{blob.path}") }
end
describe 'chooses the latest deployed environment for #environment_formatted_external_url and #environment_external_url_for_route_map' do
let(:another_external_url) { "https://another.environment" }
let(:another_environment) { create(:environment, project: project, external_url: another_external_url) }
let!(:another_deployment) { create(:deployment, :success, environment: another_environment, project: project, sha: blob.commit_id) }
it { expect(presenter.environment_formatted_external_url).to eq("another.environment") }
it { expect(presenter.environment_external_url_for_route_map).to eq("#{another_external_url}/#{blob.path}") }
end
end
describe '#code_owners' do
it { expect(presenter.code_owners).to match_array([]) }
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment