Merge pull request #149 from taigaio/us/90/github-integration

Us/90/github integration
stable
Alejandro 2014-11-18 14:07:57 +01:00
commit d335e81061
34 changed files with 639 additions and 26 deletions

View File

@ -3,9 +3,12 @@
## 1.3.0 Dryas hookeriana (Unreleased)
### Features
- GitHub integration (Phase I):
+ Add button to login/singin with a GitHub account.
+ Create Admin Panel with the GitHub webhooks settings.
- Differentiate blocked user stories on a milestone.
### Misc
### Misc
- Lots of small and not so small bugfixes.
@ -27,12 +30,12 @@
## 1.1.0 Alnus maximowiczii (2014-10-13)
### Features ###
### Features
- Promote an issue to a user story.
- Changed configuration format from coffeescript file to json.
- Add builtin analytics support.
### Misc ###
### Misc
- Fix bug related to stange behavior of browser autofill and angularjs on login page.
- Fix bug on userstories ordering on sprints.
- Fix bug of projects list visualization on project nav on first page loading.
@ -40,10 +43,10 @@
## 1.0.0 (2014-10-07)
### Features ###
### Features
- Redesign for taskboard and backlog summaries
- Allow feedback for users from the platform
- Real time changes for backlog, taskboard, kanban and issues
### Misc ###
### Misc
- Lots of small and not so small bugfixes

View File

@ -94,18 +94,20 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
{templateUrl: "/partials/admin-memberships.html"})
$routeProvider.when("/project/:pslug/admin/roles",
{templateUrl: "/partials/admin-roles.html"})
$routeProvider.when("/project/:pslug/admin/third-parties/github",
{templateUrl: "/partials/admin-third-parties-github.html"})
# User settings
$routeProvider.when("/project/:pslug/user-settings/user-profile",
{templateUrl: "/partials/user-profile.html"})
{templateUrl: "/partials/user-profile.html"})
$routeProvider.when("/project/:pslug/user-settings/user-change-password",
{templateUrl: "/partials/user-change-password.html"})
{templateUrl: "/partials/user-change-password.html"})
$routeProvider.when("/project/:pslug/user-settings/user-avatar",
{templateUrl: "/partials/user-avatar.html"})
{templateUrl: "/partials/user-avatar.html"})
$routeProvider.when("/project/:pslug/user-settings/mail-notifications",
{templateUrl: "/partials/mail-notifications.html"})
{templateUrl: "/partials/mail-notifications.html"})
$routeProvider.when("/change-email/:email_token",
{templateUrl: "/partials/change-email.html"})
{templateUrl: "/partials/change-email.html"})
$routeProvider.when("/cancel-account/:cancel_token",
{templateUrl: "/partials/cancel-account.html"})
@ -216,6 +218,7 @@ modules = [
"taigaUserSettings",
"taigaFeedback",
"taigaPlugins",
"taigaIntegrations",
# Vendor modules
"ngRoute",

View File

@ -0,0 +1,123 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: modules/admin/third-parties.coffee
###
taiga = @.taiga
mixOf = @.taiga.mixOf
module = angular.module("taigaAdmin")
#############################################################################
## Github Controller
#############################################################################
class GithubController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin)
@.$inject = [
"$scope",
"$tgRepo",
"$tgResources",
"$routeParams",
"$appTitle"
]
constructor: (@scope, @repo, @rs, @params, @appTitle) ->
_.bindAll(@)
@scope.sectionName = "Github" #i18n
@scope.project = {}
@scope.anyComputableRole = true
promise = @.loadInitialData()
promise.then () =>
@appTitle.set("Github - " + @scope.project.name)
promise.then null, @.onInitialDataError.bind(@)
loadModules: ->
return @rs.modules.list(@scope.projectId, "github").then (github) =>
@scope.github = github
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
@scope.$emit('project:loaded', project)
@scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable))
return project
loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@scope.projectId = data.project
return data
return promise.then(=> @.loadProject())
.then(=> @.loadModules())
module.controller("GithubController", GithubController)
SelectInputText = ->
link = ($scope, $el, $attrs) ->
$el.on "click", ".select-input-content", () ->
$el.find("input").select()
$el.find(".help-copy").addClass("visible")
return {link:link}
module.directive("tgSelectInputText", SelectInputText)
#############################################################################
## GithubWebhooks Directive
#############################################################################
GithubWebhooksDirective = ($repo, $confirm, $loading) ->
link = ($scope, $el, $attrs) ->
form = $el.find("form").checksley({"onlyOneErrorElement": true})
submit = (target) =>
return if not form.validate()
$loading.start(target)
promise = $repo.saveAttribute($scope.github, "github")
promise.then ->
$loading.finish(target)
$confirm.notify("success")
promise.then null, (data) ->
$loading.finish(target)
form.setErrors(data)
if data._error_message
$confirm.notify("error", data._error_message)
$el.on "click", "a.button-green", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
submit(target)
$el.on "submit", "form", (event) ->
event.preventDefault()
submit()
return {link:link}
module.directive("tgGithubWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading", GithubWebhooksDirective])

View File

@ -89,6 +89,7 @@ urls = {
"project-admin-project-values-issue-severities": "/project/:project/admin/project-values/issue-severities"
"project-admin-memberships": "/project/:project/admin/memberships"
"project-admin-roles": "/project/:project/admin/roles"
"project-admin-third-parties-github": "/project/:project/admin/third-parties/github"
# User settings
"user-settings-user-profile": "/project/:project/user-settings/user-profile"

View File

@ -31,6 +31,9 @@ class RepositoryService extends taiga.Service
idAttrName = model.getIdAttrName()
return "#{@urls.resolve(model.getName())}/#{model[idAttrName]}"
resolveUrlForAttributeModel: (model) ->
return @urls.resolve(model.getName(), model.parent)
create: (name, data, dataTypes={}, extraParams={}) ->
defered = @q.defer()
url = @urls.resolve(name)
@ -89,6 +92,37 @@ class RepositoryService extends taiga.Service
return defered.promise
saveAttribute: (model, attribute, patch=true) ->
defered = @q.defer()
if not model.isModified() and patch
defered.resolve(model)
return defered.promise
url = @.resolveUrlForAttributeModel(model)
data = {}
data[attribute] = model.getAttrs()
if patch
promise = @http.patch(url, data)
else
promise = @http.put(url, data)
promise.success (data, status) =>
model._isModified = false
model._attrs = _.extend(model.getAttrs(), data)
model._modifiedAttrs = {}
model.applyCasts()
defered.resolve(model)
promise.error (data, status) ->
defered.reject(data)
return defered.promise
refresh: (model) ->
defered = @q.defer()
@ -115,6 +149,19 @@ class RepositoryService extends taiga.Service
return @http.get(url, params, httpOptions).then (data) =>
return _.map(data.data, (x) => @model.make_model(name, x))
queryOneAttribute: (name, id, attribute, params, options={}) ->
url = @urls.resolve(name, id)
httpOptions = {headers: {}}
if not options.enablePagination
httpOptions.headers["x-disable-pagination"] = "1"
return @http.get(url, params, httpOptions).then (data) =>
model = @model.make_model(name, data.data[attribute])
model.parent = id
return model
queryOne: (name, id, params, options={}) ->
url = @urls.resolve(name)
url = "#{url}/#{id}" if id

View File

@ -132,8 +132,12 @@ CreatedByDisplayDirective = ->
link = ($scope, $el, $attrs) ->
render = (model) ->
owner = $scope.usersById?[model.owner] or {
full_name_display: "external user"
photo: "/images/unnamed.png"
}
html = template({
owner: $scope.usersById?[model.owner]
owner: owner
date: moment(model.created_date).format("DD MMM YYYY HH:mm")
})
$el.html(html)

View File

@ -97,8 +97,6 @@ Loader = () ->
startCurrentPageLoader: () ->
if config.enabled
start()
else
pageLoaded(true)
onStart: (fn) ->
$rootscope.$on("loader:start", fn)

View File

@ -0,0 +1,22 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: modules/integrations.coffee
###
module = angular.module("taigaIntegrations", [])

View File

@ -0,0 +1,111 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: modules/integrations/github.coffee
###
taiga = @.taiga
module = angular.module("taigaIntegrations")
AUTH_URL = "https://github.com/login/oauth/authorize"
#############################################################################
## User story team requirements button directive
#############################################################################
GithubLoginButtonDirective = ($window, $params, $location, $config, $events, $confirm, $auth, $navUrls, $loader) ->
# Login or registar a user with his/her github account.
#
# Example:
# tg-github-login-button()
#
# Requirements:
# - ...
template = """
<a class="button button-github" href="" title="Enter with your github account">
<span class="icon icon-github"></span>
<span>Login with Github</span>
</a>
""" #TODO: i18n
link = ($scope, $el, $attrs) ->
clientId = $config.get("gitHubClientId", null)
return if not clientId
renderGitHubButton = ->
$el.html(template) if clientId
loginOnSuccess = (response) ->
if $params.next and $params.next != $navUrls.resolve("login")
nextUrl = $params.next
else
nextUrl = $navUrls.resolve("home")
$events.setupConnection()
$location.search("next", null)
$location.search("token", null)
$location.search("state", null)
$location.search("code", null)
$location.path(nextUrl)
loginOnError = (response) ->
$location.search("state", null)
$location.search("code", null)
$loader.pageLoaded()
if response.data.error_message
$confirm.notify("light-error", response.data.error_message )
else
$confirm.notify("light-error", "Our Oompa Loompas have not been able to get you
credentials from GitHub.") #TODO: i18n
loginWithGitHubAccount = ->
type = $params.state
code = $params.code
token = $params.token
return if not (type == "github" and code)
$loader.start()
data = {code: code, token: token}
$auth.login(data, type).then(loginOnSuccess, loginOnError)
renderGitHubButton()
loginWithGitHubAccount()
$el.on "click", ".button-github", (event) ->
redirectToUri = $location.absUrl()
url = "#{AUTH_URL}?client_id=#{clientId}&redirect_uri=#{redirectToUri}&state=github&scope=user:email"
$window.location.href = url
$scope.$on "$destroy", ->
$el.off()
return {
link: link
restrict: "EA"
template: ""
}
module.directive("tgGithubLoginButton", ["$window", '$routeParams', "$tgLocation", "$tgConfig", "$tgEvents",
"$tgConfirm", "$tgAuth", "$tgNavUrls", "tgLoader",
GithubLoginButtonDirective])

View File

@ -83,6 +83,7 @@ urls = {
"issue-types": "/issue-types"
"priorities": "/priorities"
"severities": "/severities"
"project-modules": "/projects/%s/modules"
# History
"history/us": "/history/userstory"
@ -138,5 +139,6 @@ module.run([
"$tgMdRenderResourcesProvider",
"$tgHistoryResourcesProvider",
"$tgKanbanResourcesProvider",
"$tgModulesResourcesProvider",
initResources
])

View File

@ -0,0 +1,12 @@
resourceProvider = ($repo) ->
service = {}
service.list = (projectId, module) ->
return $repo.queryOneAttribute("project-modules", projectId, module)
return (instance) ->
instance.modules = service
module = angular.module("taigaResources")
module.factory("$tgModulesResourcesProvider", ["$tgRepo", resourceProvider])

BIN
app/images/github-help.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,95 @@
block head
title Taiga Your agile, free, and open source project management tool
block content
div.wrapper.roles(tg-github-webhooks, ng-controller="GithubController as ctrl",
ng-init="section='admin'")
sidebar.menu-secondary.sidebar(tg-admin-navigation="third-parties")
include views/modules/admin-menu
sidebar.menu-tertiary.sidebar(tg-admin-navigation="third-parties-github")
include views/modules/admin-submenu-third-parties
section.main.admin-common.admin-third-parties
include views/components/mainTitle
form
fieldset
label(for="secret-key") Secret key
input(type="text", name="secret-key", ng-model="github.secret", placeholder="Secret key", id="secret-key")
fieldset
.select-input-text(tg-select-input-text)
div
label(for="payload-url") Payload URL
.field-with-option
input(type="text", ng-model="github.webhooks_url", name="payload-url", readonly="readonly", placeholder="Payload URL", id="payload-url")
.option-wrapper.select-input-content
.icon.icon-copy
.help-copy Copy to clipboard: Ctrl+C
input(type="submit", class="hidden")
a.button.button-green(href="") Save
.help
h2 How to use it
h3 Configure Taiga
ol
li Fill
span Secret key
| or use the auto generated one
li Copy the
span Payload URL field.
h3 Configure Github
ol
li Go to your github repository.
li Click on
span Settings
| >
span Webhooks & Services
| >
span Add webhook
li On that screen set the payload url with the payload url of this screen.
li Secret must be filled with the same content as the secret field of this screen.
li Content type must be
span application/json.
li Taiga currently listen for three different kind of events:
ol
li Push events: changing element status via commit message
li Issues: issues created in github appear automatically in Taiga
li Issue comment: issue comments created in github appear automatically in Taiga
p Just check "send me everything" or just the events you want Taiga to listen for.
.img
.alt-image Github Webhooke page
img(src="/images/github-help.png", alt="webhook")
h2 Changing elements status via commit message
p
| The status of any issue, task or user story can be changed via commit message.
| Just add to your commit message something like:
code
| TG-REF #STATUS
ul.code-info
li
span REF:
| US/Issue/Task reference of the element you want to modify
li
span STATUS:
| New status slug to set, you can find all of them in:
a(href="", tg-nav="project-admin-project-values-us-status:project=project.slug") US STATUSES.
h3 An example please!
code
| TG-123 #closed
p In this example, 123 is an issue reference and with this command, the issue will change its status to closed.

View File

@ -23,6 +23,10 @@ block content
tg-nav="project-userstories-detail:project=project.slug, ref=us.ref")
span(tg-bo-ref="us.ref")
p.external-reference(ng-if="issue.external_reference") This issue has been created from
a(target="_blank", tg-bo-href="issue.external_reference[1]", title="Go to origin")
span {{ issue.external_reference[1] }}
p.block-desc-container(ng-show="issue.is_blocked")
span.block-description-title Blocked
span.block-description(ng-bind="issue.blocked_note || 'This issue is blocked'")

View File

@ -21,12 +21,18 @@ block content
h2.us-title-text
span.us-number(tg-bo-ref="task.ref")
span.us-name(tg-editable-subject, ng-model="task", required-perm="modify_task")
h3.us-related-task This task belongs to
a(tg-check-permission="view_us", href="", title="Go to user story",
tg-nav="project-userstories-detail:project=project.slug, ref=us.ref",
ng-if="us")
span(tg-bo-ref="us.ref")
span(tg-bo-bind="us.subject")
p.external-reference(ng-if="task.external_reference") This task has been created from
a(target="_blank", tg-bo-href="task.external_reference[1]", title="Go to origin")
span {{ task.external_reference[1] }}
p.block-desc-container(ng-show="task.is_blocked")
span.block-description-title Blocked
span.block-description(ng-bind="task.blocked_note || 'This task is blocked'")

View File

@ -28,6 +28,10 @@ block content
tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject")
span(tg-bo-ref="us.origin_issue.ref")
p.external-reference(ng-if="us.external_reference") This US has been created from
a(target="_blank", tg-bo-href="us.external_reference[1]", title="Go to origin")
span {{ us.external_reference[1] }}
p.block-desc-container(ng-show="us.is_blocked")
span.block-description-title Blocked
span.block-description(ng-bind="us.blocked_note || 'This user story is blocked'")

View File

@ -20,3 +20,7 @@ section.admin-menu
a(href="" tg-nav="project-admin-roles:project=project.slug")
span.title Roles & Permissions
span.icon.icon-arrow-right
li#adminmenu-third-parties
a(href="" tg-nav="project-admin-third-parties-github:project=project.slug")
span.title Third parties
span.icon.icon-arrow-right

View File

@ -0,0 +1,10 @@
section.admin-submenu
header
h1 Third parties
nav
ul
li#adminmenu-third-parties-github
a(href="", tg-nav="project-admin-third-parties-github:project=project.slug")
span.title Github
span.icon.icon-arrow-right

View File

@ -3,6 +3,7 @@ section.colors-table
div.row
div.color-column Color
div.status-name Name
div.status-slug Slug
div.is-closed-column Is closed?
div.options-column
@ -17,6 +18,9 @@ section.colors-table
div.status-name
span {{ value.name }}
div.status-slug
span {{ value.slug }}
div.is-closed-column
div.icon.icon-check-square(ng-show="value.is_closed")

View File

@ -3,6 +3,7 @@ section.colors-table
div.row
div.color-column Color
div.status-name Name
div.status-slug Slug
div.is-closed-column Is closed?
div.status-wip-limit WIP Limit
div.options-column
@ -19,6 +20,9 @@ section.colors-table
div.status-name
span {{ value.name }}
div.status-slug
span {{ value.slug }}
div.is-closed-column
div.icon.icon-check-square(ng-show="value.is_closed")

View File

@ -10,3 +10,5 @@ form.login-form
fieldset
a.button.button-login.button-gray(href="", title="Log in") Enter
input(type="submit", style="display:none")
fieldset(tg-github-login-button)

View File

@ -23,4 +23,4 @@ form.register-form
a.button.button-register.button-gray(href="", title="Sign up") Sign up
input(type="submit", style="display:none")
tg-terms-notice
tg-terms-notice

View File

@ -13,4 +13,6 @@ div.login-form-container(tg-login)
a.button.button-login.button-gray(href="", title="Sign in") Sign in
input(type="submit", style="display:none")
fieldset(tg-github-login-button)
tg-public-register-message

View File

@ -17,13 +17,15 @@ div.register-form-container(tg-register)
fieldset
input(type="password", name="password", ng-model="data.password",
data-required="true", data-minlength="4",
data-required="true", data-minlength="4",
placeholder="Set a password (case sensitive)")
fieldset
a.button.button-register.button-gray(href="", title="Sign up") Sign up
input(type="submit", class="hidden")
fieldset(tg-github-login-button)
// Only displays terms notice when terms plugin is loaded.
tg-terms-notice

View File

@ -34,7 +34,7 @@ a.button-green {
a.button-gray {
background: $button-gray;
&:hover {
background: $button-gray-hover;
background: $fresh-taiga;
color: $white;
}
span {
@ -102,3 +102,18 @@ a.button-bulk {
background: $fresh-taiga;
}
}
.button-github {
@extend %button;
background: $grayer;
vertical-align: middle;
.icon {
@extend %large;
color: $white;
margin-right: .5rem;
vertical-align: text-bottom;
}
&:hover {
@include transition (background .3s linear);
background: $black;
}
}

View File

@ -44,10 +44,12 @@
}
.invitation-form {
@include table-flex();
fieldset {
margin-bottom: .5rem;
}
input {
background: $white;
color: $gray;
margin-bottom: 1rem;
position: relative;
@include placeholder {
color: $gray-light;
@ -80,16 +82,28 @@
background: $fresh-taiga;
}
}
.button-github {
&:hover {
background: $black;
}
}
}
.login-form,
.register-form {
@include table-flex-child(1, 200px, 0, 200px);
padding: 1rem;
text-align: center;
.form-header {
color: #999;
}
}
.register-form {
fieldset {
&:last-child {
margin-bottom: 1rem;
}
}
}
.register-text {
@extend %small;
color: $white;

View File

@ -1,5 +1,3 @@
.login-main {
//@include table-flex(center, center, flex, row, wrap, center);
@include display(flex);
@ -60,11 +58,7 @@
.button {
color: $white;
display: block;
margin-bottom: .5rem;
text-align: center;
&:hover {
background: $fresh-taiga;
}
}
a {
&:hover {

View File

@ -0,0 +1,91 @@
.admin-third-parties {
form {
margin-top: 1rem;
max-width: 700px;
width: 100%;
}
input[type="text"],
textarea {
@extend %title;
}
fieldset {
margin-bottom: 1rem;
}
label {
@extend %title;
display: block;
margin-bottom: .2rem;
}
textarea {
height: 10rem;
}
.button-green {
color: $white;
display: block;
text-align: center;
}
.select-input-text {
.field-with-option {
@include display(flex);
}
.option-wrapper {
@include display(flex);
@include align-items(center);
border: 1px solid $gray-light;
border-left: 0;
border-radius: 0 5px 5px 0;
cursor: pointer;
padding: 0 1rem;
}
.help-copy {
@extend %small;
opacity: 0;
&.visible {
@include transition(opacity .2s linear);
opacity: 1;
}
}
}
.help {
margin-top: 2rem;
h3 {
font-family: opensans-semibold;
margin-bottom: 1rem;
}
ol {
padding: 0 0 0 2rem;
span {
font-family: opensans-semibold;
}
}
.img {
margin-bottom: 1rem;
}
.alt-image {
@extend %small;
font-style: italic;
}
code {
@extend %small;
background: $whitish;
direction: ltr;
display: block;
font-family: 'courier new', 'monospace';
line-height: 1.4rem;
margin-bottom: 1rem;
padding: .5rem;
unicode-bidi: embed;
white-space: pre;
width: 100%;
}
.code-info {
padding-left: 1rem;
li {
margin-bottom: .5rem;
}
span {
font-family: opensans-semibold;
}
}
}
}

View File

@ -1,5 +1,4 @@
.login-form-container {
//display: none;
.login-password {
position: relative;
}

View File

@ -50,6 +50,10 @@
display: block;
}
}
.status-slug {
@include table-flex-child(6, 150px, 0);
padding: 0 10px;
}
.options-column {
max-width: 100px;
opacity: 0;

View File

@ -0,0 +1,29 @@
.blocked {
.external-reference {
color: $white;
a {
@include transition(color .3s linear);
color: $white;
&:hover {
color: $red-light;
}
}
}
}
.external-reference {
@extend %small;
color: $gray-light;
margin-top: .5rem;
a {
@include transition(color .3s linear);
border-left: 1px solid $gray-light;
padding: 0 .2rem;
&:hover {
color: $green-taiga;
}
&:first-child {
border: 0;
}
}
}

View File

@ -6,5 +6,6 @@
"feedbackEnabled": true,
"privacyPolicyUrl": null,
"termsOfServiceUrl": null,
"maxUploadFileSize": null
"maxUploadFileSize": null,
"gitHubClientId": null
}

View File

@ -73,6 +73,7 @@ paths.coffee = [
paths.app + "coffee/modules/base/*.coffee",
paths.app + "coffee/modules/resources/*.coffee",
paths.app + "coffee/modules/user-settings/*.coffee"
paths.app + "coffee/modules/integrations/*.coffee"
paths.app + "plugins/**/*.coffee"
]

View File

@ -71,6 +71,7 @@ exports.files = function () {
'modules/common/related-tasks',
'modules/common/history',
'modules/common/wizard',
'modules/common/external-reference',
//Project modules
'modules/home-projects-list',
@ -120,6 +121,7 @@ exports.files = function () {
'modules/admin/admin-project-profile',
'modules/admin/default-values',
'modules/admin/project-values',
'modules/admin/third-parties',
//Modules user Settings
'modules/user-settings/user-profile',