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 { ...@@ -92,6 +92,8 @@ export default {
:active-viewer="viewer" :active-viewer="viewer"
:has-render-error="hasRenderError" :has-render-error="hasRenderError"
:is-binary="isBinary" :is-binary="isBinary"
:environment-name="blob.environmentFormattedExternalUrl"
:environment-path="blob.environmentExternalUrlForRouteMap"
@copy="proxyCopyRequest" @copy="proxyCopyRequest"
/> />
</div> </div>
......
<script> <script>
import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui'; import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import { import {
BTN_COPY_CONTENTS_TITLE, BTN_COPY_CONTENTS_TITLE,
BTN_DOWNLOAD_TITLE, BTN_DOWNLOAD_TITLE,
...@@ -37,6 +38,16 @@ export default { ...@@ -37,6 +38,16 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
environmentName: {
type: String,
required: false,
default: null,
},
environmentPath: {
type: String,
required: false,
default: null,
},
}, },
computed: { computed: {
downloadUrl() { downloadUrl() {
...@@ -51,6 +62,11 @@ export default { ...@@ -51,6 +62,11 @@ export default {
showCopyButton() { showCopyButton() {
return !this.hasRenderError && !this.isBinary; return !this.hasRenderError && !this.isBinary;
}, },
environmentTitle() {
return sprintf(s__('BlobViewer|View on %{environmentName}'), {
environmentName: this.environmentName,
});
},
}, },
BTN_COPY_CONTENTS_TITLE, BTN_COPY_CONTENTS_TITLE,
BTN_DOWNLOAD_TITLE, BTN_DOWNLOAD_TITLE,
...@@ -93,5 +109,17 @@ export default { ...@@ -93,5 +109,17 @@ export default {
category="primary" category="primary"
variant="default" 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> </gl-button-group>
</template> </template>
...@@ -106,6 +106,8 @@ export default { ...@@ -106,6 +106,8 @@ export default {
ideForkAndEditPath: '', ideForkAndEditPath: '',
storedExternally: false, storedExternally: false,
externalStorage: '', externalStorage: '',
environmentFormattedExternalUrl: '',
environmentExternalUrlForRouteMap: '',
canModifyBlob: false, canModifyBlob: false,
canCurrentUserPushToBranch: false, canCurrentUserPushToBranch: false,
archived: false, archived: false,
......
...@@ -25,6 +25,8 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) { ...@@ -25,6 +25,8 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
ideEditPath ideEditPath
forkAndEditPath forkAndEditPath
ideForkAndEditPath ideForkAndEditPath
environmentFormattedExternalUrl
environmentExternalUrlForRouteMap
canModifyBlob canModifyBlob
canCurrentUserPushToBranch canCurrentUserPushToBranch
archived archived
......
...@@ -15,8 +15,8 @@ module Environments ...@@ -15,8 +15,8 @@ module Environments
deployments = deployments =
if ref if ref
Deployment.where(ref: ref.to_s) Deployment.where(ref: ref.to_s)
elsif commit elsif sha
Deployment.where(sha: commit.sha) Deployment.where(sha: sha)
else else
Deployment.none Deployment.none
end end
...@@ -47,7 +47,7 @@ module Environments ...@@ -47,7 +47,7 @@ module Environments
return false unless Ability.allowed?(current_user, :read_environment, environment) 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 && 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 true
end end
...@@ -56,8 +56,8 @@ module Environments ...@@ -56,8 +56,8 @@ module Environments
params[:ref].try(:to_s) params[:ref].try(:to_s)
end end
def commit def sha
params[:commit] params[:sha] || params[:commit]&.id
end end
end end
end end
...@@ -87,6 +87,14 @@ module Types ...@@ -87,6 +87,14 @@ module Types
description: 'Web path to blob permalink.', description: 'Web path to blob permalink.',
calls_gitaly: true 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, field :code_owners, [Types::UserType], null: true,
description: 'List of code owners for the blob.', description: 'List of code owners for the blob.',
calls_gitaly: true calls_gitaly: true
......
...@@ -255,10 +255,10 @@ class Deployment < ApplicationRecord ...@@ -255,10 +255,10 @@ class Deployment < ApplicationRecord
end end
end end
def includes_commit?(commit) def includes_commit?(ancestor_sha)
return false unless commit return false unless sha
project.repository.ancestor?(commit.id, sha) project.repository.ancestor?(ancestor_sha, sha)
end end
def update_merge_request_metrics! def update_merge_request_metrics!
......
...@@ -235,10 +235,10 @@ class Environment < ApplicationRecord ...@@ -235,10 +235,10 @@ class Environment < ApplicationRecord
self.environment_type = names.many? ? names.first : nil self.environment_type = names.many? ? names.first : nil
end end
def includes_commit?(commit) def includes_commit?(sha)
return false unless last_deployment return false unless last_deployment
last_deployment.includes_commit?(commit) last_deployment.includes_commit?(sha)
end end
def last_deployed_at def last_deployed_at
......
...@@ -79,6 +79,18 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated ...@@ -79,6 +79,18 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
url_helpers.project_blob_path(project, File.join(project.repository.commit.sha, blob.path)) url_helpers.project_blob_path(project, File.join(project.repository.commit.sha, blob.path))
end 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 # Will be overridden in EE
def code_owners def code_owners
[] []
...@@ -122,6 +134,12 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated ...@@ -122,6 +134,12 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
Gitlab::Routing.url_helpers Gitlab::Routing.url_helpers
end 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 def project
blob.repository.project blob.repository.project
end end
......
...@@ -14620,6 +14620,8 @@ Returns [`Tree`](#tree). ...@@ -14620,6 +14620,8 @@ Returns [`Tree`](#tree).
| <a id="repositoryblobcanmodifyblob"></a>`canModifyBlob` | [`Boolean`](#boolean) | Whether the current user can modify the blob. | | <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="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="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="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="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. | | <a id="repositoryblobfiletype"></a>`fileType` | [`String`](#string) | Expected format of the blob based on the extension. |
...@@ -5631,6 +5631,9 @@ msgstr "" ...@@ -5631,6 +5631,9 @@ msgstr ""
msgid "Blame" msgid "Blame"
msgstr "" msgstr ""
msgid "BlobViewer|View on %{environmentName}"
msgstr ""
msgid "Block user" msgid "Block user"
msgstr "" msgstr ""
......
...@@ -9,7 +9,6 @@ RSpec.describe 'View on environment', :js do ...@@ -9,7 +9,6 @@ RSpec.describe 'View on environment', :js do
let(:user) { project.creator } let(:user) { project.creator }
before do 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) project.add_maintainer(user)
end end
......
...@@ -64,6 +64,22 @@ RSpec.describe Environments::EnvironmentsByDeploymentsFinder do ...@@ -64,6 +64,22 @@ RSpec.describe Environments::EnvironmentsByDeploymentsFinder do
end end
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 context 'commit deployment' do
before do before do
create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id) create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id)
......
import { GlButtonGroup, GlButton } from '@gitlab/ui'; import { GlButtonGroup, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import BlobHeaderActions from '~/blob/components/blob_header_default_actions.vue'; import BlobHeaderActions from '~/blob/components/blob_header_default_actions.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { import {
BTN_COPY_CONTENTS_TITLE, BTN_COPY_CONTENTS_TITLE,
BTN_DOWNLOAD_TITLE, BTN_DOWNLOAD_TITLE,
BTN_RAW_TITLE, BTN_RAW_TITLE,
RICH_BLOB_VIEWER, RICH_BLOB_VIEWER,
} from '~/blob/components/constants'; } from '~/blob/components/constants';
import { Blob } from './mock_data'; import { Blob, mockEnvironmentName, mockEnvironmentPath } from './mock_data';
describe('Blob Header Default Actions', () => { describe('Blob Header Default Actions', () => {
let wrapper; let wrapper;
...@@ -17,7 +17,7 @@ describe('Blob Header Default Actions', () => { ...@@ -17,7 +17,7 @@ describe('Blob Header Default Actions', () => {
const blobHash = 'foo-bar'; const blobHash = 'foo-bar';
function createComponent(propsData = {}) { function createComponent(propsData = {}) {
wrapper = mount(BlobHeaderActions, { wrapper = shallowMountExtended(BlobHeaderActions, {
provide: { provide: {
blobHash, blobHash,
}, },
...@@ -39,8 +39,8 @@ describe('Blob Header Default Actions', () => { ...@@ -39,8 +39,8 @@ describe('Blob Header Default Actions', () => {
}); });
describe('renders', () => { describe('renders', () => {
const findCopyButton = () => wrapper.find('[data-testid="copyContentsButton"]'); const findCopyButton = () => wrapper.findByTestId('copyContentsButton');
const findViewRawButton = () => wrapper.find('[data-testid="viewRawButton"]'); const findViewRawButton = () => wrapper.findByTestId('viewRawButton');
it('gl-button-group component', () => { it('gl-button-group component', () => {
expect(btnGroup.exists()).toBe(true); expect(btnGroup.exists()).toBe(true);
...@@ -89,4 +89,37 @@ describe('Blob Header Default Actions', () => { ...@@ -89,4 +89,37 @@ describe('Blob Header Default Actions', () => {
expect(findViewRawButton().exists()).toBe(false); 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 = { ...@@ -55,3 +55,6 @@ export const SimpleBlobContentMock = {
path: 'foo.js', path: 'foo.js',
plainData: 'Plain', plainData: 'Plain',
}; };
export const mockEnvironmentName = 'my.testing.environment';
export const mockEnvironmentPath = 'https://my.testing.environment';
...@@ -11,6 +11,8 @@ export const simpleViewerMock = { ...@@ -11,6 +11,8 @@ export const simpleViewerMock = {
ideEditPath: 'some_file.js/ide/edit', ideEditPath: 'some_file.js/ide/edit',
forkAndEditPath: 'some_file.js/fork/edit', forkAndEditPath: 'some_file.js/fork/edit',
ideForkAndEditPath: 'some_file.js/fork/ide', ideForkAndEditPath: 'some_file.js/fork/ide',
environmentFormattedExternalUrl: '',
environmentExternalUrlForRouteMap: '',
canModifyBlob: true, canModifyBlob: true,
canCurrentUserPushToBranch: true, canCurrentUserPushToBranch: true,
archived: false, archived: false,
......
...@@ -29,6 +29,8 @@ RSpec.describe Types::Repository::BlobType do ...@@ -29,6 +29,8 @@ RSpec.describe Types::Repository::BlobType do
:blame_path, :blame_path,
:history_path, :history_path,
:permalink_path, :permalink_path,
:environment_formatted_external_url,
:environment_external_url_for_route_map,
:code_owners, :code_owners,
:simple_viewer, :simple_viewer,
:rich_viewer, :rich_viewer,
......
...@@ -615,7 +615,7 @@ RSpec.describe Deployment do ...@@ -615,7 +615,7 @@ RSpec.describe Deployment do
it 'returns false' do it 'returns false' do
commit = project.commit('feature') commit = project.commit('feature')
expect(deployment.includes_commit?(commit)).to be false expect(deployment.includes_commit?(commit.id)).to be false
end end
end end
...@@ -623,7 +623,7 @@ RSpec.describe Deployment do ...@@ -623,7 +623,7 @@ RSpec.describe Deployment do
it 'returns true' do it 'returns true' do
commit = project.commit commit = project.commit
expect(deployment.includes_commit?(commit)).to be true expect(deployment.includes_commit?(commit.id)).to be true
end end
end end
...@@ -632,7 +632,7 @@ RSpec.describe Deployment do ...@@ -632,7 +632,7 @@ RSpec.describe Deployment do
deployment.update!(sha: Gitlab::Git::BLANK_SHA) deployment.update!(sha: Gitlab::Git::BLANK_SHA)
commit = project.commit commit = project.commit
expect(deployment.includes_commit?(commit)).to be false expect(deployment.includes_commit?(commit.id)).to be false
end end
end end
end end
......
...@@ -412,7 +412,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do ...@@ -412,7 +412,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
context 'in the same branch' do context 'in the same branch' do
it 'returns true' 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
end end
...@@ -422,7 +422,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do ...@@ -422,7 +422,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
end end
it 'returns false' do 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 end
end end
......
...@@ -87,6 +87,33 @@ RSpec.describe BlobPresenter do ...@@ -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") } it { expect(presenter.permalink_path).to eq("/#{project.full_path}/-/blob/#{project.repository.commit.sha}/files/ruby/regex.rb") }
end 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 describe '#code_owners' do
it { expect(presenter.code_owners).to match_array([]) } it { expect(presenter.code_owners).to match_array([]) }
end 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