Show upcoming status for releases
Add released_at field to releases API Add released_at column to releases table Return releases to the API sorted by released_at
This commit is contained in:
parent
d6391c6503
commit
7ecffe2987
|
|
@ -28,7 +28,7 @@ export default {
|
|||
computed: {
|
||||
releasedTimeAgo() {
|
||||
return sprintf(__('released %{time}'), {
|
||||
time: this.timeFormated(this.release.created_at),
|
||||
time: this.timeFormated(this.release.released_at),
|
||||
});
|
||||
},
|
||||
userImageAltDescription() {
|
||||
|
|
@ -56,8 +56,8 @@ export default {
|
|||
<div class="card-body">
|
||||
<h2 class="card-title mt-0">
|
||||
{{ release.name }}
|
||||
<gl-badge v-if="release.pre_release" variant="warning" class="align-middle">{{
|
||||
__('Pre-release')
|
||||
<gl-badge v-if="release.upcoming_release" variant="warning" class="align-middle">{{
|
||||
__('Upcoming Release')
|
||||
}}</gl-badge>
|
||||
</h2>
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ export default {
|
|||
|
||||
<div class="append-right-4">
|
||||
•
|
||||
<span v-gl-tooltip.bottom :title="tooltipTitle(release.created_at)">
|
||||
<span v-gl-tooltip.bottom :title="tooltipTitle(release.released_at)">
|
||||
{{ releasedTimeAgo }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,12 +12,16 @@ class Release < ApplicationRecord
|
|||
|
||||
has_many :links, class_name: 'Releases::Link'
|
||||
|
||||
default_value_for :released_at, allows_nil: false do
|
||||
Time.zone.now
|
||||
end
|
||||
|
||||
accepts_nested_attributes_for :links, allow_destroy: true
|
||||
|
||||
validates :description, :project, :tag, presence: true
|
||||
validates :name, presence: true, on: :create
|
||||
|
||||
scope :sorted, -> { order(created_at: :desc) }
|
||||
scope :sorted, -> { order(released_at: :desc) }
|
||||
|
||||
delegate :repository, to: :project
|
||||
|
||||
|
|
@ -44,6 +48,10 @@ class Release < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def upcoming_release?
|
||||
released_at.present? && released_at > Time.zone.now
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def actual_sha
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ module Releases
|
|||
params[:description]
|
||||
end
|
||||
|
||||
def released_at
|
||||
params[:released_at]
|
||||
end
|
||||
|
||||
def release
|
||||
strong_memoize(:release) do
|
||||
project.releases.find_by_tag(tag_name)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ module Releases
|
|||
author: current_user,
|
||||
tag: tag.name,
|
||||
sha: tag.dereferenced_target.sha,
|
||||
released_at: released_at,
|
||||
links_attributes: params.dig(:assets, 'links') || []
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show an Upcoming Status for Releases
|
||||
merge_request: 29577
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddReleasedAtToReleasesTable < ActiveRecord::Migration[5.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column(:releases, :released_at, :datetime_with_timezone)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable < ActiveRecord::Migration[5.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
update_column_in_batches(:releases, :released_at, Arel.sql('created_at'))
|
||||
change_column_null(:releases, :released_at, false)
|
||||
end
|
||||
|
||||
def down
|
||||
change_column_null(:releases, :released_at, true)
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20190628145246) do
|
||||
ActiveRecord::Schema.define(version: 20190628185004) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
|
@ -2902,6 +2902,7 @@ ActiveRecord::Schema.define(version: 20190628145246) do
|
|||
t.integer "author_id"
|
||||
t.string "name"
|
||||
t.string "sha"
|
||||
t.datetime_with_timezone "released_at", null: false
|
||||
t.index ["author_id"], name: "index_releases_on_author_id", using: :btree
|
||||
t.index ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree
|
||||
t.index ["project_id"], name: "index_releases_on_project_id", using: :btree
|
||||
|
|
|
|||
|
|
@ -1186,8 +1186,10 @@ module API
|
|||
MarkupHelper.markdown_field(entity, :description)
|
||||
end
|
||||
expose :created_at
|
||||
expose :released_at
|
||||
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
|
||||
expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? }
|
||||
expose :upcoming_release?, as: :upcoming_release
|
||||
|
||||
expose :assets do
|
||||
expose :assets_count, as: :count do |release, _|
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ module API
|
|||
requires :url, type: String
|
||||
end
|
||||
end
|
||||
optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.'
|
||||
end
|
||||
post ':id/releases' do
|
||||
authorize_create_release!
|
||||
|
|
@ -77,6 +78,7 @@ module API
|
|||
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
|
||||
optional :name, type: String, desc: 'The name of the release'
|
||||
optional :description, type: String, desc: 'Release notes with markdown support'
|
||||
optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.'
|
||||
end
|
||||
put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
|
||||
authorize_update_release!
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ module Gitlab
|
|||
name: raw_data.name,
|
||||
description: raw_data.body,
|
||||
created_at: raw_data.created_at,
|
||||
released_at: raw_data.published_at,
|
||||
updated_at: raw_data.created_at
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7574,9 +7574,6 @@ msgstr ""
|
|||
msgid "Please wait while we import the repository for you. Refresh at will."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pre-release"
|
||||
msgstr ""
|
||||
|
||||
msgid "Preferences"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11378,6 +11375,9 @@ msgstr ""
|
|||
msgid "Upcoming"
|
||||
msgstr ""
|
||||
|
||||
msgid "Upcoming Release"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ FactoryBot.define do
|
|||
description "Awesome release"
|
||||
project
|
||||
author
|
||||
released_at { Time.zone.parse('2018-10-20T18:00:00Z') }
|
||||
|
||||
trait :legacy do
|
||||
sha nil
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ describe 'User views releases', :js do
|
|||
|
||||
expect(page).to have_content(release.name)
|
||||
expect(page).to have_content(release.tag)
|
||||
expect(page).not_to have_content('Upcoming Release')
|
||||
end
|
||||
|
||||
context 'when there is a link as an asset' do
|
||||
|
|
@ -43,4 +44,15 @@ describe 'User views releases', :js do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an upcoming release' do
|
||||
let(:tomorrow) { Time.zone.now + 1.day }
|
||||
let!(:release) { create(:release, project: project, released_at: tomorrow ) }
|
||||
|
||||
it 'sees the upcoming tag' do
|
||||
visit project_releases_path(project)
|
||||
|
||||
expect(page).to have_content('Upcoming Release')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ describe ReleasesFinder do
|
|||
subject { described_class.new(project, user)}
|
||||
|
||||
before do
|
||||
v1_0_0.update_attribute(:created_at, 2.days.ago)
|
||||
v1_1_0.update_attribute(:created_at, 1.day.ago)
|
||||
v1_0_0.update_attribute(:released_at, 2.days.ago)
|
||||
v1_1_0.update_attribute(:released_at, 1.day.ago)
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
|
|
@ -30,7 +30,7 @@ describe ReleasesFinder do
|
|||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'sorts by creation date' do
|
||||
it 'sorts by release date' do
|
||||
releases = subject.execute
|
||||
|
||||
expect(releases).to be_present
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required": ["name", "tag_name", "commit"],
|
||||
"required": ["name", "tag_name", "commit", "released_at"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"tag_name": { "type": "string" },
|
||||
"description": { "type": "string" },
|
||||
"description_html": { "type": "string" },
|
||||
"created_at": { "type": "date" },
|
||||
"released_at": { "type": "date" },
|
||||
"upcoming_release": { "type": "boolean" },
|
||||
"commit": {
|
||||
"oneOf": [{ "type": "null" }, { "$ref": "commit/basic.json" }]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"required": ["name", "released_at"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": "string" },
|
||||
"description_html": { "type": "string" },
|
||||
"created_at": { "type": "date" },
|
||||
"released_at": { "type": "date" },
|
||||
"upcoming_release": { "type": "boolean" },
|
||||
"author": {
|
||||
"oneOf": [{ "type": "null" }, { "$ref": "../user/basic.json" }]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ describe('Release block', () => {
|
|||
description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
|
||||
author_name: 'Release bot',
|
||||
author_email: 'release-bot@example.com',
|
||||
created_at: '2012-05-28T05:00:00-07:00',
|
||||
released_at: '2012-05-28T05:00:00-07:00',
|
||||
author: {
|
||||
avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
|
||||
id: 482476,
|
||||
|
|
@ -101,7 +101,7 @@ describe('Release block', () => {
|
|||
});
|
||||
|
||||
it('renders release date', () => {
|
||||
expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.created_at));
|
||||
expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at));
|
||||
});
|
||||
|
||||
it('renders number of assets provided', () => {
|
||||
|
|
@ -152,13 +152,13 @@ describe('Release block', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('with pre_release flag', () => {
|
||||
describe('with upcoming_release flag', () => {
|
||||
beforeEach(() => {
|
||||
vm = factory(Object.assign({}, release, { pre_release: true }));
|
||||
vm = factory(Object.assign({}, release, { upcoming_release: true }));
|
||||
});
|
||||
|
||||
it('renders pre-release badge', () => {
|
||||
expect(vm.$el.textContent).toContain('Pre-release');
|
||||
it('renders upcoming release badge', () => {
|
||||
expect(vm.$el.textContent).toContain('Upcoming Release');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ Release:
|
|||
- project_id
|
||||
- created_at
|
||||
- updated_at
|
||||
- released_at
|
||||
Releases::Link:
|
||||
- id
|
||||
- release_id
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ describe Gitlab::LegacyGithubImport::Importer do
|
|||
body: 'Release v1.0.0',
|
||||
draft: false,
|
||||
created_at: created_at,
|
||||
published_at: created_at,
|
||||
updated_at: updated_at,
|
||||
url: "#{api_root}/repos/octocat/Hello-World/releases/1"
|
||||
)
|
||||
|
|
@ -144,6 +145,7 @@ describe Gitlab::LegacyGithubImport::Importer do
|
|||
body: nil,
|
||||
draft: false,
|
||||
created_at: created_at,
|
||||
published_at: created_at,
|
||||
updated_at: updated_at,
|
||||
url: "#{api_root}/repos/octocat/Hello-World/releases/2"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do
|
|||
let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
|
||||
let(:octocat) { double(id: 123456, login: 'octocat') }
|
||||
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
|
||||
let(:published_at) { DateTime.strptime('2011-01-26T20:00:00Z') }
|
||||
|
||||
let(:base_data) do
|
||||
{
|
||||
|
|
@ -11,7 +12,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do
|
|||
name: 'First release',
|
||||
draft: false,
|
||||
created_at: created_at,
|
||||
published_at: created_at,
|
||||
published_at: published_at,
|
||||
body: 'Release v1.0.0'
|
||||
}
|
||||
end
|
||||
|
|
@ -28,6 +29,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do
|
|||
name: 'First release',
|
||||
description: 'Release v1.0.0',
|
||||
created_at: created_at,
|
||||
released_at: published_at,
|
||||
updated_at: created_at
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require Rails.root.join('db', 'migrate', '20190628185004_backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table.rb')
|
||||
|
||||
describe BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable, :migration do
|
||||
let(:releases) { table(:releases) }
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
let(:projects) { table(:projects) }
|
||||
|
||||
subject(:migration) { described_class.new }
|
||||
|
||||
it 'fills released_at with the value of created_at' do
|
||||
created_at_a = Time.zone.parse('2019-02-10T08:00:00Z')
|
||||
created_at_b = Time.zone.parse('2019-03-10T18:00:00Z')
|
||||
namespace = namespaces.create(name: 'foo', path: 'foo')
|
||||
project = projects.create!(namespace_id: namespace.id)
|
||||
release_a = releases.create!(project_id: project.id, created_at: created_at_a)
|
||||
release_b = releases.create!(project_id: project.id, created_at: created_at_b)
|
||||
|
||||
disable_migrations_output { migration.up }
|
||||
|
||||
release_a.reload
|
||||
release_b.reload
|
||||
expect(release_a.released_at).to eq(created_at_a)
|
||||
expect(release_b.released_at).to eq(created_at_b)
|
||||
end
|
||||
end
|
||||
|
|
@ -64,4 +64,14 @@ RSpec.describe Release do
|
|||
is_expected.to all(be_a(Releases::Source))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#upcoming_release?' do
|
||||
context 'during the backfill migration when released_at could be nil' do
|
||||
it 'handles a nil released_at value and returns false' do
|
||||
allow(release).to receive(:released_at).and_return nil
|
||||
|
||||
expect(release.upcoming_release?).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ describe API::Releases do
|
|||
project: project,
|
||||
tag: 'v0.1',
|
||||
author: maintainer,
|
||||
created_at: 2.days.ago)
|
||||
released_at: 2.days.ago)
|
||||
end
|
||||
|
||||
let!(:release_2) do
|
||||
|
|
@ -32,7 +32,7 @@ describe API::Releases do
|
|||
project: project,
|
||||
tag: 'v0.2',
|
||||
author: maintainer,
|
||||
created_at: 1.day.ago)
|
||||
released_at: 1.day.ago)
|
||||
end
|
||||
|
||||
it 'returns 200 HTTP status' do
|
||||
|
|
@ -41,7 +41,7 @@ describe API::Releases do
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'returns releases ordered by created_at' do
|
||||
it 'returns releases ordered by released_at' do
|
||||
get api("/projects/#{project.id}/releases", maintainer)
|
||||
|
||||
expect(json_response.count).to eq(2)
|
||||
|
|
@ -56,6 +56,26 @@ describe API::Releases do
|
|||
end
|
||||
end
|
||||
|
||||
it 'returns an upcoming_release status for a future release' do
|
||||
tomorrow = Time.now.utc + 1.day
|
||||
create(:release, project: project, tag: 'v0.1', author: maintainer, released_at: tomorrow)
|
||||
|
||||
get api("/projects/#{project.id}/releases", maintainer)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.first['upcoming_release']).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns an upcoming_release status for a past release' do
|
||||
yesterday = Time.now.utc - 1.day
|
||||
create(:release, project: project, tag: 'v0.1', author: maintainer, released_at: yesterday)
|
||||
|
||||
get api("/projects/#{project.id}/releases", maintainer)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.first['upcoming_release']).to eq(false)
|
||||
end
|
||||
|
||||
context 'when tag does not exist in git repository' do
|
||||
let!(:release) { create(:release, project: project, tag: 'v1.1.5') }
|
||||
|
||||
|
|
@ -316,6 +336,51 @@ describe API::Releases do
|
|||
expect(project.releases.last.description).to eq('Super nice release')
|
||||
end
|
||||
|
||||
it 'sets the released_at to the current time if the released_at parameter is not provided' do
|
||||
now = Time.zone.parse('2015-08-25 06:00:00Z')
|
||||
Timecop.freeze(now) do
|
||||
post api("/projects/#{project.id}/releases", maintainer), params: params
|
||||
|
||||
expect(project.releases.last.released_at).to eq(now)
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets the released_at to the value in the parameters if specified' do
|
||||
params = {
|
||||
name: 'New release',
|
||||
tag_name: 'v0.1',
|
||||
description: 'Super nice release',
|
||||
released_at: '2019-03-20T10:00:00Z'
|
||||
}
|
||||
post api("/projects/#{project.id}/releases", maintainer), params: params
|
||||
|
||||
expect(project.releases.last.released_at).to eq('2019-03-20T10:00:00Z')
|
||||
end
|
||||
|
||||
it 'assumes the utc timezone for released_at if the timezone is not provided' do
|
||||
params = {
|
||||
name: 'New release',
|
||||
tag_name: 'v0.1',
|
||||
description: 'Super nice release',
|
||||
released_at: '2019-03-25 10:00:00'
|
||||
}
|
||||
post api("/projects/#{project.id}/releases", maintainer), params: params
|
||||
|
||||
expect(project.releases.last.released_at).to eq('2019-03-25T10:00:00Z')
|
||||
end
|
||||
|
||||
it 'allows specifying a released_at with a local time zone' do
|
||||
params = {
|
||||
name: 'New release',
|
||||
tag_name: 'v0.1',
|
||||
description: 'Super nice release',
|
||||
released_at: '2019-03-25T10:00:00+09:00'
|
||||
}
|
||||
post api("/projects/#{project.id}/releases", maintainer), params: params
|
||||
|
||||
expect(project.releases.last.released_at).to eq('2019-03-25T01:00:00Z')
|
||||
end
|
||||
|
||||
context 'when description is empty' do
|
||||
let(:params) do
|
||||
{
|
||||
|
|
@ -540,6 +605,7 @@ describe API::Releases do
|
|||
project: project,
|
||||
tag: 'v0.1',
|
||||
name: 'New release',
|
||||
released_at: '2018-03-01T22:00:00Z',
|
||||
description: 'Super nice release')
|
||||
end
|
||||
|
||||
|
|
@ -560,6 +626,7 @@ describe API::Releases do
|
|||
|
||||
expect(project.releases.last.tag).to eq('v0.1')
|
||||
expect(project.releases.last.name).to eq('New release')
|
||||
expect(project.releases.last.released_at).to eq('2018-03-01T22:00:00Z')
|
||||
end
|
||||
|
||||
it 'matches response schema' do
|
||||
|
|
@ -568,6 +635,14 @@ describe API::Releases do
|
|||
expect(response).to match_response_schema('public_api/v4/release')
|
||||
end
|
||||
|
||||
it 'updates released_at' do
|
||||
params = { released_at: '2015-10-10T05:00:00Z' }
|
||||
|
||||
put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params
|
||||
|
||||
expect(project.releases.last.released_at).to eq('2015-10-10T05:00:00Z')
|
||||
end
|
||||
|
||||
context 'when user tries to update sha' do
|
||||
let(:params) { { sha: 'xxx' } }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue