web/satellite,satellite/console: Allow paid tier users to edit limits
Added editable fields to the project details page for Storage Limit and Bandwidth limit. Leveraged existing types when possible. Added fixed checking into the limits to prevent reducing limits beyond current usage, as well as limiting usage to less than the default paid tier maximum. Change-Id: I07ce53470919a8a9d4dce56ade6904ede8daf34c
This commit is contained in:
parent
1fa0cfbfe0
commit
cc083dbdc9
@ -43,6 +43,10 @@ const (
|
|||||||
|
|
||||||
// InputArg is argument name for all input types.
|
// InputArg is argument name for all input types.
|
||||||
InputArg = "input"
|
InputArg = "input"
|
||||||
|
// ProjectFields is a field name for project specific fields.
|
||||||
|
ProjectFields = "projectFields"
|
||||||
|
// ProjectLimits is a field name for project specific limits.
|
||||||
|
ProjectLimits = "projectLimits"
|
||||||
// FieldProjectID is field name for projectID.
|
// FieldProjectID is field name for projectID.
|
||||||
FieldProjectID = "projectID"
|
FieldProjectID = "projectID"
|
||||||
// FieldNewPassword is a field name for new password.
|
// FieldNewPassword is a field name for new password.
|
||||||
@ -96,16 +100,15 @@ func rootMutation(log *zap.Logger, service *console.Service, mailService *mailse
|
|||||||
FieldID: &graphql.ArgumentConfig{
|
FieldID: &graphql.ArgumentConfig{
|
||||||
Type: graphql.NewNonNull(graphql.String),
|
Type: graphql.NewNonNull(graphql.String),
|
||||||
},
|
},
|
||||||
FieldName: &graphql.ArgumentConfig{
|
ProjectFields: &graphql.ArgumentConfig{
|
||||||
Type: graphql.NewNonNull(graphql.String),
|
Type: graphql.NewNonNull(types.projectInput),
|
||||||
},
|
},
|
||||||
FieldDescription: &graphql.ArgumentConfig{
|
ProjectLimits: &graphql.ArgumentConfig{
|
||||||
Type: graphql.NewNonNull(graphql.String),
|
Type: graphql.NewNonNull(types.projectLimit),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||||
name := p.Args[FieldName].(string)
|
var projectInput = fromMapProjectInfoProjectLimits(p.Args[ProjectFields].(map[string]interface{}), p.Args[ProjectLimits].(map[string]interface{}))
|
||||||
description := p.Args[FieldDescription].(string)
|
|
||||||
|
|
||||||
inputID := p.Args[FieldID].(string)
|
inputID := p.Args[FieldID].(string)
|
||||||
projectID, err := uuid.FromString(inputID)
|
projectID, err := uuid.FromString(inputID)
|
||||||
@ -113,7 +116,7 @@ func rootMutation(log *zap.Logger, service *console.Service, mailService *mailse
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
project, err := service.UpdateProject(p.Context, projectID, name, description)
|
project, err := service.UpdateProject(p.Context, projectID, projectInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -366,13 +366,17 @@ func TestGraphqlMutation(t *testing.T) {
|
|||||||
|
|
||||||
const testName = "testName"
|
const testName = "testName"
|
||||||
const testDescription = "test description"
|
const testDescription = "test description"
|
||||||
|
const StorageLimit = "100"
|
||||||
|
const BandwidthLimit = "100"
|
||||||
|
|
||||||
t.Run("Update project mutation", func(t *testing.T) {
|
t.Run("Update project mutation", func(t *testing.T) {
|
||||||
query := fmt.Sprintf(
|
query := fmt.Sprintf(
|
||||||
"mutation {updateProject(id:\"%s\",name:\"%s\",description:\"%s\"){id,name,description}}",
|
"mutation {updateProject(id:\"%s\",projectFields:{name:\"%s\",description:\"%s\"},projectLimits:{storageLimit:\"%s\",bandwidthLimit:\"%s\"}){id,name,description}}",
|
||||||
project.ID.String(),
|
project.ID.String(),
|
||||||
testName,
|
testName,
|
||||||
testDescription,
|
testDescription,
|
||||||
|
StorageLimit,
|
||||||
|
BandwidthLimit,
|
||||||
)
|
)
|
||||||
|
|
||||||
result, err := testQuery(t, query)
|
result, err := testQuery(t, query)
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
package consoleql
|
package consoleql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/graphql-go/graphql"
|
"github.com/graphql-go/graphql"
|
||||||
|
|
||||||
|
"storj.io/common/memory"
|
||||||
"storj.io/storj/satellite/accounting"
|
"storj.io/storj/satellite/accounting"
|
||||||
"storj.io/storj/satellite/console"
|
"storj.io/storj/satellite/console"
|
||||||
)
|
)
|
||||||
@ -17,6 +19,8 @@ const (
|
|||||||
ProjectType = "project"
|
ProjectType = "project"
|
||||||
// ProjectInputType is a graphql type name for project input.
|
// ProjectInputType is a graphql type name for project input.
|
||||||
ProjectInputType = "projectInput"
|
ProjectInputType = "projectInput"
|
||||||
|
// ProjectLimitType is a graphql type name for project limit.
|
||||||
|
ProjectLimitType = "projectLimit"
|
||||||
// ProjectUsageType is a graphql type name for project usage.
|
// ProjectUsageType is a graphql type name for project usage.
|
||||||
ProjectUsageType = "projectUsage"
|
ProjectUsageType = "projectUsage"
|
||||||
// ProjectsCursorInputType is a graphql input type name for projects cursor.
|
// ProjectsCursorInputType is a graphql input type name for projects cursor.
|
||||||
@ -54,6 +58,10 @@ const (
|
|||||||
FieldUsage = "usage"
|
FieldUsage = "usage"
|
||||||
// FieldBucketUsages is a field name for bucket usages.
|
// FieldBucketUsages is a field name for bucket usages.
|
||||||
FieldBucketUsages = "bucketUsages"
|
FieldBucketUsages = "bucketUsages"
|
||||||
|
// FieldStorageLimit is a field name for the storage limit.
|
||||||
|
FieldStorageLimit = "storageLimit"
|
||||||
|
// FieldBandwidthLimit is a field name for bandwidth limit.
|
||||||
|
FieldBandwidthLimit = "bandwidthLimit"
|
||||||
// FieldStorage is a field name for storage total.
|
// FieldStorage is a field name for storage total.
|
||||||
FieldStorage = "storage"
|
FieldStorage = "storage"
|
||||||
// FieldEgress is a field name for egress total.
|
// FieldEgress is a field name for egress total.
|
||||||
@ -266,6 +274,21 @@ func graphqlProjectInput() *graphql.InputObject {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// graphqlProjectLimit creates graphql.InputObject type needed to create/update satellite.Project.
|
||||||
|
func graphqlProjectLimit() *graphql.InputObject {
|
||||||
|
return graphql.NewInputObject(graphql.InputObjectConfig{
|
||||||
|
Name: ProjectLimitType,
|
||||||
|
Fields: graphql.InputObjectConfigFieldMap{
|
||||||
|
FieldStorageLimit: &graphql.InputObjectFieldConfig{
|
||||||
|
Type: graphql.String,
|
||||||
|
},
|
||||||
|
FieldBandwidthLimit: &graphql.InputObjectFieldConfig{
|
||||||
|
Type: graphql.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// graphqlBucketUsageCursor creates bucket usage cursor graphql input type.
|
// graphqlBucketUsageCursor creates bucket usage cursor graphql input type.
|
||||||
func graphqlProjectsCursor() *graphql.InputObject {
|
func graphqlProjectsCursor() *graphql.InputObject {
|
||||||
return graphql.NewInputObject(graphql.InputObjectConfig{
|
return graphql.NewInputObject(graphql.InputObjectConfig{
|
||||||
@ -415,6 +438,18 @@ func fromMapProjectInfo(args map[string]interface{}) (project console.ProjectInf
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fromMapProjectInfoProjectLimits creates console.ProjectInfo from input args.
|
||||||
|
func fromMapProjectInfoProjectLimits(projectInfo, projectLimits map[string]interface{}) (project console.ProjectInfo) {
|
||||||
|
project.Name, _ = projectInfo[FieldName].(string)
|
||||||
|
project.Description, _ = projectInfo[FieldDescription].(string)
|
||||||
|
storageLimit, _ := strconv.Atoi(projectLimits[FieldStorageLimit].(string))
|
||||||
|
project.StorageLimit = memory.Size(storageLimit)
|
||||||
|
bandwidthLimit, _ := strconv.Atoi(projectLimits[FieldBandwidthLimit].(string))
|
||||||
|
project.BandwidthLimit = memory.Size(bandwidthLimit)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// fromMapProjectsCursor creates console.ProjectsCursor from input args.
|
// fromMapProjectsCursor creates console.ProjectsCursor from input args.
|
||||||
func fromMapProjectsCursor(args map[string]interface{}) (cursor console.ProjectsCursor) {
|
func fromMapProjectsCursor(args map[string]interface{}) (cursor console.ProjectsCursor) {
|
||||||
cursor.Limit = args[LimitArg].(int)
|
cursor.Limit = args[LimitArg].(int)
|
||||||
|
@ -31,6 +31,7 @@ type TypeCreator struct {
|
|||||||
|
|
||||||
userInput *graphql.InputObject
|
userInput *graphql.InputObject
|
||||||
projectInput *graphql.InputObject
|
projectInput *graphql.InputObject
|
||||||
|
projectLimit *graphql.InputObject
|
||||||
projectsCursor *graphql.InputObject
|
projectsCursor *graphql.InputObject
|
||||||
bucketUsageCursor *graphql.InputObject
|
bucketUsageCursor *graphql.InputObject
|
||||||
projectMembersCursor *graphql.InputObject
|
projectMembersCursor *graphql.InputObject
|
||||||
@ -50,6 +51,11 @@ func (c *TypeCreator) Create(log *zap.Logger, service *console.Service, mailServ
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.projectLimit = graphqlProjectLimit()
|
||||||
|
if err := c.projectLimit.Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
c.bucketUsageCursor = graphqlBucketUsageCursor()
|
c.bucketUsageCursor = graphqlBucketUsageCursor()
|
||||||
if err := c.bucketUsageCursor.Error(); err != nil {
|
if err := c.bucketUsageCursor.Error(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -82,10 +82,11 @@ type Project struct {
|
|||||||
|
|
||||||
// ProjectInfo holds data needed to create/update Project.
|
// ProjectInfo holds data needed to create/update Project.
|
||||||
type ProjectInfo struct {
|
type ProjectInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
StorageLimit memory.Size `json:"project specific storage limit"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
BandwidthLimit memory.Size `json:"project specific bandwidth limit"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProjectsCursor holds info for project
|
// ProjectsCursor holds info for project
|
||||||
|
@ -1181,7 +1181,7 @@ func (s *Service) DeleteProject(ctx context.Context, projectID uuid.UUID) (err e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateProject is a method for updating project name and description by id.
|
// UpdateProject is a method for updating project name and description by id.
|
||||||
func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, name string, description string) (p *Project, err error) {
|
func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, projectInfo ProjectInfo) (p *Project, err error) {
|
||||||
defer mon.Task()(&ctx)(&err)
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
auth, err := s.getAuthAndAuditLog(ctx, "update project name and description", zap.String("projectID", projectID.String()))
|
auth, err := s.getAuthAndAuditLog(ctx, "update project name and description", zap.String("projectID", projectID.String()))
|
||||||
@ -1189,7 +1189,7 @@ func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, name s
|
|||||||
return nil, Error.Wrap(err)
|
return nil, Error.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ValidateNameAndDescription(name, description)
|
err = ValidateNameAndDescription(projectInfo.Name, projectInfo.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, Error.Wrap(err)
|
return nil, Error.Wrap(err)
|
||||||
}
|
}
|
||||||
@ -1199,8 +1199,43 @@ func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, name s
|
|||||||
return nil, Error.Wrap(err)
|
return nil, Error.Wrap(err)
|
||||||
}
|
}
|
||||||
project := isMember.project
|
project := isMember.project
|
||||||
project.Name = name
|
project.Name = projectInfo.Name
|
||||||
project.Description = description
|
project.Description = projectInfo.Description
|
||||||
|
|
||||||
|
if auth.User.PaidTier {
|
||||||
|
if projectInfo.StorageLimit.Int64() <= 0 || projectInfo.BandwidthLimit.Int64() <= 0 {
|
||||||
|
return project, errors.New("project limits must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if projectInfo.StorageLimit.Int64() > s.config.UsageLimits.Storage.Paid.Int64() {
|
||||||
|
return project, errors.New("specified storage limit exceeds allowed maximum for current tier")
|
||||||
|
}
|
||||||
|
|
||||||
|
if projectInfo.BandwidthLimit.Int64() > s.config.UsageLimits.Bandwidth.Paid.Int64() {
|
||||||
|
return project, errors.New("specified bandwidth limit exceeds allowed maximum for current tier")
|
||||||
|
}
|
||||||
|
|
||||||
|
storageUsed, err := s.projectUsage.GetProjectStorageTotals(ctx, projectID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, Error.Wrap(err)
|
||||||
|
}
|
||||||
|
if projectInfo.StorageLimit.Int64() < storageUsed {
|
||||||
|
return project, errors.New("cannot set storage limit below current usage")
|
||||||
|
}
|
||||||
|
|
||||||
|
bandwidthUsed, err := s.projectUsage.GetProjectBandwidthTotals(ctx, projectID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, Error.Wrap(err)
|
||||||
|
}
|
||||||
|
if projectInfo.BandwidthLimit.Int64() < bandwidthUsed {
|
||||||
|
return project, errors.New("cannot set bandwidth limit below current usage")
|
||||||
|
}
|
||||||
|
|
||||||
|
project.StorageLimit = new(memory.Size)
|
||||||
|
*project.StorageLimit = projectInfo.StorageLimit
|
||||||
|
project.BandwidthLimit = new(memory.Size)
|
||||||
|
*project.BandwidthLimit = projectInfo.BandwidthLimit
|
||||||
|
}
|
||||||
|
|
||||||
err = s.store.Projects().Update(ctx, project)
|
err = s.store.Projects().Update(ctx, project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -59,15 +59,51 @@ func TestService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("TestUpdateProject", func(t *testing.T) {
|
t.Run("TestUpdateProject", func(t *testing.T) {
|
||||||
// Updating own project should work
|
updatedName := "newName"
|
||||||
updatedPro, err := service.UpdateProject(authCtx1, up1Pro1.ID, "newName", "TestUpdate")
|
updatedDescription := "newDescription"
|
||||||
|
updatedStorageLimit := memory.Size(100)
|
||||||
|
updatedBandwidthLimit := memory.Size(100)
|
||||||
|
|
||||||
|
// user should be in free tier
|
||||||
|
user, err := service.GetUser(ctx, up1Pro1.OwnerID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, up1Pro1.Name, updatedPro.Name)
|
require.False(t, user.PaidTier)
|
||||||
|
// get context
|
||||||
|
authCtx1, err := sat.AuthenticatedContext(ctx, user.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// add a credit card to put the user in the paid tier
|
||||||
|
err = service.Payments().AddCreditCard(authCtx1, "test-cc-token")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// update auth ctx
|
||||||
|
authCtx1, err = sat.AuthenticatedContext(ctx, user.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Updating own project should work
|
||||||
|
updatedProject, err := service.UpdateProject(authCtx1, up1Pro1.ID, console.ProjectInfo{
|
||||||
|
Name: updatedName,
|
||||||
|
Description: updatedDescription,
|
||||||
|
StorageLimit: updatedStorageLimit,
|
||||||
|
BandwidthLimit: updatedBandwidthLimit,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEqual(t, up1Pro1.Name, updatedProject.Name)
|
||||||
|
require.Equal(t, updatedName, updatedProject.Name)
|
||||||
|
require.NotEqual(t, up1Pro1.Description, updatedProject.Description)
|
||||||
|
require.Equal(t, updatedDescription, updatedProject.Description)
|
||||||
|
require.NotEqual(t, *up1Pro1.StorageLimit, *updatedProject.StorageLimit)
|
||||||
|
require.Equal(t, updatedStorageLimit, *updatedProject.StorageLimit)
|
||||||
|
require.NotEqual(t, *up1Pro1.BandwidthLimit, *updatedProject.BandwidthLimit)
|
||||||
|
require.Equal(t, updatedBandwidthLimit, *updatedProject.BandwidthLimit)
|
||||||
|
|
||||||
// Updating someone else project details should not work
|
// Updating someone else project details should not work
|
||||||
updatedPro, err = service.UpdateProject(authCtx1, up2Pro1.ID, "newName", "TestUpdate")
|
updatedProject, err = service.UpdateProject(authCtx1, up2Pro1.ID, console.ProjectInfo{
|
||||||
|
Name: "newName",
|
||||||
|
Description: "TestUpdate",
|
||||||
|
StorageLimit: memory.Size(100),
|
||||||
|
BandwidthLimit: memory.Size(100),
|
||||||
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, updatedPro)
|
require.Nil(t, updatedProject)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("TestAddProjectMembers", func(t *testing.T) {
|
t.Run("TestAddProjectMembers", func(t *testing.T) {
|
||||||
|
@ -83,20 +83,28 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
|
|||||||
* @returns Project[]
|
* @returns Project[]
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public async update(projectId: string, name: string, description: string): Promise<void> {
|
public async update(projectId: string, projectFields: ProjectFields, projectLimits: ProjectLimits): Promise<void> {
|
||||||
const query =
|
const query =
|
||||||
`mutation($projectId: String!, $name: String!, $description: String!) {
|
`mutation($projectId: String!, $name: String!, $description: String!, $storageLimit: String!, $bandwidthLimit: String!) {
|
||||||
updateProject(
|
updateProject(
|
||||||
id: $projectId,
|
id: $projectId,
|
||||||
name: $name,
|
projectFields: {
|
||||||
description: $description
|
name: $name,
|
||||||
|
description: $description,
|
||||||
|
},
|
||||||
|
projectLimits: {
|
||||||
|
storageLimit: $storageLimit,
|
||||||
|
bandwidthLimit: $bandwidthLimit,
|
||||||
|
}
|
||||||
) {name}
|
) {name}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const variables = {
|
const variables = {
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
name: name,
|
name: projectFields.name,
|
||||||
description: description,
|
description: projectFields.description,
|
||||||
|
storageLimit: projectLimits.storageLimit.toString(),
|
||||||
|
bandwidthLimit: projectLimits.bandwidthLimit.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.mutate(query, variables);
|
await this.mutate(query, variables);
|
||||||
|
@ -63,19 +63,83 @@
|
|||||||
:on-press="onSaveDescriptionButtonClick"
|
:on-press="onSaveDescriptionButtonClick"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isPaidTier" class="project-details__wrapper__container__limits">
|
||||||
|
<p class="project-details__wrapper__container__limits__label">Storage Limit</p>
|
||||||
|
<div v-if="!isStorageLimitEditing" class="project-details__wrapper__container__limits__storagelimit-area">
|
||||||
|
<p class="project-details__wrapper__container__limits__storagelimit-area__storagelimit">{{ storageLimitFormatted }}</p>
|
||||||
|
<VButton
|
||||||
|
label="Edit"
|
||||||
|
width="64px"
|
||||||
|
height="28px"
|
||||||
|
:on-press="toggleStorageLimitEditing"
|
||||||
|
is-white="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="isStorageLimitEditing" class="project-details__wrapper__container__limits__storagelimit-editing">
|
||||||
|
<input
|
||||||
|
v-model="storageLimitValue"
|
||||||
|
class="project-details__wrapper__container__limits__storagelimit-editing__input"
|
||||||
|
placeholder="Enter a storage limit for your project"
|
||||||
|
@input="onStorageLimitInput"
|
||||||
|
@change="onStorageLimitInput"
|
||||||
|
>
|
||||||
|
<span class="project-details__wrapper__container__limits__storagelimit-editing__limit">{{ nameValue.length }}/{{ nameLength }}</span>
|
||||||
|
<VButton
|
||||||
|
class="project-details__wrapper__container__limits__storagelimit-editing__save-button"
|
||||||
|
label="Save"
|
||||||
|
width="66px"
|
||||||
|
height="30px"
|
||||||
|
:on-press="onSaveStorageLimitButtonClick"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="project-details__wrapper__container__limits__label">Bandwidth Limit</p>
|
||||||
|
<div v-if="!isBandwidthLimitEditing" class="project-details__wrapper__container__limits__bandwidthlimit-area">
|
||||||
|
<p class="project-details__wrapper__container__limits__bandwidthlimit-area__bandwidthlimit">{{ bandwidthLimitFormatted }}</p>
|
||||||
|
<VButton
|
||||||
|
label="Edit"
|
||||||
|
width="64px"
|
||||||
|
height="28px"
|
||||||
|
:on-press="toggleBandwidthLimitEditing"
|
||||||
|
is-white="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="isBandwidthLimitEditing" class="project-details__wrapper__container__limits__bandwidthlimit-editing">
|
||||||
|
<input
|
||||||
|
v-model="bandwidthLimitValue"
|
||||||
|
class="project-details__wrapper__container__limits__bandwidthlimit-editing__input"
|
||||||
|
placeholder="Enter a bandwidth limit for your project"
|
||||||
|
@input="onBandwidthLimitInput"
|
||||||
|
@change="onBandwidthLimitInput"
|
||||||
|
>
|
||||||
|
<span class="project-details__wrapper__container__limits__bandwidthlimit-editing__limit">{{ nameValue.length }}/{{ nameLength }}</span>
|
||||||
|
<VButton
|
||||||
|
class="project-details__wrapper__container__limits__bandwidthlimit-editing__save-button"
|
||||||
|
label="Save"
|
||||||
|
width="66px"
|
||||||
|
height="30px"
|
||||||
|
:on-press="onSaveBandwidthLimitButtonClick"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { Dimensions, Size } from '@/utils/bytesSize';
|
||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
|
|
||||||
import HeaderedInput from '@/components/common/HeaderedInput.vue';
|
import HeaderedInput from '@/components/common/HeaderedInput.vue';
|
||||||
import VButton from '@/components/common/VButton.vue';
|
import VButton from '@/components/common/VButton.vue';
|
||||||
|
|
||||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||||
import { MAX_DESCRIPTION_LENGTH, MAX_NAME_LENGTH, Project, ProjectFields } from '@/types/projects';
|
import {
|
||||||
|
MAX_DESCRIPTION_LENGTH,
|
||||||
|
MAX_NAME_LENGTH,
|
||||||
|
Project,
|
||||||
|
ProjectFields, ProjectLimits
|
||||||
|
} from '@/types/projects';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@ -86,8 +150,13 @@ import { MAX_DESCRIPTION_LENGTH, MAX_NAME_LENGTH, Project, ProjectFields } from
|
|||||||
export default class EditProjectDetails extends Vue {
|
export default class EditProjectDetails extends Vue {
|
||||||
public isNameEditing = false;
|
public isNameEditing = false;
|
||||||
public isDescriptionEditing = false;
|
public isDescriptionEditing = false;
|
||||||
|
public isStorageLimitEditing = false;
|
||||||
|
public isBandwidthLimitEditing = false;
|
||||||
|
public isPaidTier = false;
|
||||||
public nameValue = '';
|
public nameValue = '';
|
||||||
public descriptionValue = '';
|
public descriptionValue = '';
|
||||||
|
public storageLimitValue = 0;
|
||||||
|
public bandwidthLimitValue = 0;
|
||||||
public nameLength: number = MAX_NAME_LENGTH;
|
public nameLength: number = MAX_NAME_LENGTH;
|
||||||
public descriptionLength: number = MAX_DESCRIPTION_LENGTH;
|
public descriptionLength: number = MAX_DESCRIPTION_LENGTH;
|
||||||
|
|
||||||
@ -98,6 +167,34 @@ export default class EditProjectDetails extends Vue {
|
|||||||
return this.$store.getters.selectedProject;
|
return this.$store.getters.selectedProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook after initial render.
|
||||||
|
* Fetches project limits and paid tier status.
|
||||||
|
*/
|
||||||
|
public async mounted(): Promise<void> {
|
||||||
|
if (!this.$store.getters.selectedProject.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$store.state.usersModule.user.paidTier)
|
||||||
|
{
|
||||||
|
this.isPaidTier = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.$store.getters.selectedProject.id);
|
||||||
|
} catch (error) {
|
||||||
|
await this.$notify.error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current limits from store.
|
||||||
|
*/
|
||||||
|
public get currentLimits(): ProjectLimits {
|
||||||
|
return this.$store.state.projectsModule.currentLimits;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns displayed project description on UI.
|
* Returns displayed project description on UI.
|
||||||
*/
|
*/
|
||||||
@ -133,6 +230,49 @@ export default class EditProjectDetails extends Vue {
|
|||||||
this.descriptionValue = target.value.slice(0, MAX_DESCRIPTION_LENGTH);
|
this.descriptionValue = target.value.slice(0, MAX_DESCRIPTION_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns formatted limit amount.
|
||||||
|
*/
|
||||||
|
public get storageLimitFormatted(): string {
|
||||||
|
return this.formattedValue(new Size(this.currentLimits.storageLimit, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers on storage limit input.
|
||||||
|
*/
|
||||||
|
public onStorageLimitInput(event: Event): void {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
this.storageLimitValue = parseInt(target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns formatted limit amount.
|
||||||
|
*/
|
||||||
|
public get bandwidthLimitFormatted(): string {
|
||||||
|
return this.formattedValue(new Size(this.currentLimits.bandwidthLimit, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers on bandwidth limit input.
|
||||||
|
*/
|
||||||
|
public onBandwidthLimitInput(event: Event): void {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
this.bandwidthLimitValue = parseInt(target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats value to needed form and returns it.
|
||||||
|
*/
|
||||||
|
private formattedValue(value: Size): string {
|
||||||
|
switch (value.label) {
|
||||||
|
case Dimensions.Bytes:
|
||||||
|
case Dimensions.KB:
|
||||||
|
return '0';
|
||||||
|
default:
|
||||||
|
return `${value.formattedBytes.replace(/\\.0+$/, '')}${value.label}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates project name.
|
* Updates project name.
|
||||||
*/
|
*/
|
||||||
@ -169,6 +309,40 @@ export default class EditProjectDetails extends Vue {
|
|||||||
await this.$notify.success('Project description updated successfully!');
|
await this.$notify.success('Project description updated successfully!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates project storage limit.
|
||||||
|
*/
|
||||||
|
public async onSaveStorageLimitButtonClick(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const updatedProject = new ProjectLimits(0, 0, this.storageLimitValue);
|
||||||
|
await this.$store.dispatch(PROJECTS_ACTIONS.UPDATE_STORAGE_LIMIT, updatedProject);
|
||||||
|
} catch (error) {
|
||||||
|
await this.$notify.error(`Unable to update project storage limit. ${error.message}`);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleStorageLimitEditing();
|
||||||
|
await this.$notify.success('Project storage limit updated successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates project bandwidth limit.
|
||||||
|
*/
|
||||||
|
public async onSaveBandwidthLimitButtonClick(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const updatedProject = new ProjectLimits(this.bandwidthLimitValue);
|
||||||
|
await this.$store.dispatch(PROJECTS_ACTIONS.UPDATE_BANDWIDTH_LIMIT, updatedProject);
|
||||||
|
} catch (error) {
|
||||||
|
await this.$notify.error(`Unable to update project bandwidth limit. ${error.message}`);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleBandwidthLimitEditing();
|
||||||
|
await this.$notify.success('Project bandwidth limit updated successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles project name editing state.
|
* Toggles project name editing state.
|
||||||
*/
|
*/
|
||||||
@ -185,6 +359,26 @@ export default class EditProjectDetails extends Vue {
|
|||||||
this.descriptionValue = this.storedProject.description;
|
this.descriptionValue = this.storedProject.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles project storage limit editing state.
|
||||||
|
*/
|
||||||
|
public toggleStorageLimitEditing(): void {
|
||||||
|
if (this.$store.state.usersModule.user.paidTier) {
|
||||||
|
this.isStorageLimitEditing = !this.isStorageLimitEditing;
|
||||||
|
this.storageLimitValue = this.currentLimits.storageLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles project bandwidth limit editing state.
|
||||||
|
*/
|
||||||
|
public toggleBandwidthLimitEditing(): void {
|
||||||
|
if (this.$store.state.usersModule.user.paidTier) {
|
||||||
|
this.isBandwidthLimitEditing = !this.isBandwidthLimitEditing;
|
||||||
|
this.bandwidthLimitValue = this.currentLimits.bandwidthLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirects to previous route.
|
* Redirects to previous route.
|
||||||
*/
|
*/
|
||||||
@ -240,7 +434,9 @@ export default class EditProjectDetails extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__name-area,
|
&__name-area,
|
||||||
&__description-area {
|
&__description-area,
|
||||||
|
&__limits__storagelimit-area,
|
||||||
|
&__limits__bandwidthlimit-area {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -250,7 +446,9 @@ export default class EditProjectDetails extends Vue {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
||||||
&__name,
|
&__name,
|
||||||
&__description {
|
&__description,
|
||||||
|
&__limits__storagelimit,
|
||||||
|
&__limits__bandwidthlimit {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 19px;
|
line-height: 19px;
|
||||||
@ -264,12 +462,16 @@ export default class EditProjectDetails extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name-area {
|
&__name-area,
|
||||||
|
&__description-area,
|
||||||
|
&__limits__storagelimit-area {
|
||||||
margin-bottom: 35px;
|
margin-bottom: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name-editing,
|
&__name-editing,
|
||||||
&__description-editing {
|
&__description-editing,
|
||||||
|
&__limits__storagelimit-editing,
|
||||||
|
&__limits__bandwidthlimit-editing {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: calc(100% - 7px);
|
width: calc(100% - 7px);
|
||||||
@ -308,7 +510,9 @@ export default class EditProjectDetails extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name-editing {
|
&__name-editing,
|
||||||
|
&__description-editing,
|
||||||
|
&___limits_storagelimit-editing {
|
||||||
margin-bottom: 35px;
|
margin-bottom: 35px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ export const PROJECTS_ACTIONS = {
|
|||||||
SELECT: 'selectProject',
|
SELECT: 'selectProject',
|
||||||
UPDATE_NAME: 'updateProjectName',
|
UPDATE_NAME: 'updateProjectName',
|
||||||
UPDATE_DESCRIPTION: 'updateProjectDescription',
|
UPDATE_DESCRIPTION: 'updateProjectDescription',
|
||||||
|
UPDATE_STORAGE_LIMIT: 'updateProjectStorageLimit',
|
||||||
|
UPDATE_BANDWIDTH_LIMIT: 'updateProjectBandwidthLimit',
|
||||||
DELETE: 'deleteProject',
|
DELETE: 'deleteProject',
|
||||||
CLEAR: 'clearProjects',
|
CLEAR: 'clearProjects',
|
||||||
GET_LIMITS: 'getProjectLimits',
|
GET_LIMITS: 'getProjectLimits',
|
||||||
@ -30,6 +32,8 @@ export const PROJECTS_MUTATIONS = {
|
|||||||
REMOVE: 'DELETE_PROJECT',
|
REMOVE: 'DELETE_PROJECT',
|
||||||
UPDATE_PROJECT_NAME: 'UPDATE_PROJECT_NAME',
|
UPDATE_PROJECT_NAME: 'UPDATE_PROJECT_NAME',
|
||||||
UPDATE_PROJECT_DESCRIPTION: 'UPDATE_PROJECT_DESCRIPTION',
|
UPDATE_PROJECT_DESCRIPTION: 'UPDATE_PROJECT_DESCRIPTION',
|
||||||
|
UPDATE_PROJECT_STORAGE_LIMIT: 'UPDATE_STORAGE_LIMIT',
|
||||||
|
UPDATE_PROJECT_BANDWIDTH_LIMIT: 'UPDATE_BANDWIDTH_LIMIT',
|
||||||
SET_PROJECTS: 'SET_PROJECTS',
|
SET_PROJECTS: 'SET_PROJECTS',
|
||||||
SELECT_PROJECT: 'SELECT_PROJECT',
|
SELECT_PROJECT: 'SELECT_PROJECT',
|
||||||
CLEAR_PROJECTS: 'CLEAR_PROJECTS',
|
CLEAR_PROJECTS: 'CLEAR_PROJECTS',
|
||||||
@ -64,6 +68,8 @@ const {
|
|||||||
SELECT,
|
SELECT,
|
||||||
UPDATE_NAME,
|
UPDATE_NAME,
|
||||||
UPDATE_DESCRIPTION,
|
UPDATE_DESCRIPTION,
|
||||||
|
UPDATE_STORAGE_LIMIT,
|
||||||
|
UPDATE_BANDWIDTH_LIMIT,
|
||||||
DELETE,
|
DELETE,
|
||||||
CLEAR,
|
CLEAR,
|
||||||
GET_LIMITS,
|
GET_LIMITS,
|
||||||
@ -76,6 +82,8 @@ const {
|
|||||||
REMOVE,
|
REMOVE,
|
||||||
UPDATE_PROJECT_NAME,
|
UPDATE_PROJECT_NAME,
|
||||||
UPDATE_PROJECT_DESCRIPTION,
|
UPDATE_PROJECT_DESCRIPTION,
|
||||||
|
UPDATE_PROJECT_STORAGE_LIMIT,
|
||||||
|
UPDATE_PROJECT_BANDWIDTH_LIMIT,
|
||||||
SET_PROJECTS,
|
SET_PROJECTS,
|
||||||
SELECT_PROJECT,
|
SELECT_PROJECT,
|
||||||
CLEAR_PROJECTS,
|
CLEAR_PROJECTS,
|
||||||
@ -131,6 +139,12 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
|
|||||||
[UPDATE_PROJECT_DESCRIPTION](state: ProjectsState, fieldsToUpdate: ProjectFields): void {
|
[UPDATE_PROJECT_DESCRIPTION](state: ProjectsState, fieldsToUpdate: ProjectFields): void {
|
||||||
state.selectedProject.description = fieldsToUpdate.description;
|
state.selectedProject.description = fieldsToUpdate.description;
|
||||||
},
|
},
|
||||||
|
[UPDATE_PROJECT_STORAGE_LIMIT](state: ProjectsState, limitsToUpdate: ProjectLimits): void {
|
||||||
|
state.currentLimits.storageLimit = limitsToUpdate.storageLimit;
|
||||||
|
},
|
||||||
|
[UPDATE_PROJECT_BANDWIDTH_LIMIT](state: ProjectsState, limitsToUpdate: ProjectLimits): void {
|
||||||
|
state.currentLimits.bandwidthLimit = limitsToUpdate.bandwidthLimit;
|
||||||
|
},
|
||||||
[REMOVE](state: ProjectsState, projectID: string): void {
|
[REMOVE](state: ProjectsState, projectID: string): void {
|
||||||
state.projects = state.projects.filter(project => project.id !== projectID);
|
state.projects = state.projects.filter(project => project.id !== projectID);
|
||||||
|
|
||||||
@ -196,16 +210,70 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
|
|||||||
commit(SELECT_PROJECT, projectID);
|
commit(SELECT_PROJECT, projectID);
|
||||||
},
|
},
|
||||||
[UPDATE_NAME]: async function ({commit, state}: ProjectsContext, fieldsToUpdate: ProjectFields): Promise<void> {
|
[UPDATE_NAME]: async function ({commit, state}: ProjectsContext, fieldsToUpdate: ProjectFields): Promise<void> {
|
||||||
await api.update(state.selectedProject.id, fieldsToUpdate.name, state.selectedProject.description);
|
const project = new ProjectFields(
|
||||||
|
fieldsToUpdate.name,
|
||||||
|
state.selectedProject.description,
|
||||||
|
state.selectedProject.id,
|
||||||
|
);
|
||||||
|
const limit = new ProjectLimits(
|
||||||
|
state.currentLimits.bandwidthLimit,
|
||||||
|
state.currentLimits.bandwidthUsed,
|
||||||
|
state.currentLimits.storageLimit,
|
||||||
|
state.currentLimits.storageUsed,
|
||||||
|
);
|
||||||
|
await api.update(state.selectedProject.id, project, limit);
|
||||||
|
|
||||||
commit(UPDATE_PROJECT_NAME, fieldsToUpdate);
|
commit(UPDATE_PROJECT_NAME, fieldsToUpdate);
|
||||||
},
|
},
|
||||||
[UPDATE_DESCRIPTION]: async function ({commit, state}: ProjectsContext, fieldsToUpdate: ProjectFields): Promise<void> {
|
[UPDATE_DESCRIPTION]: async function ({commit, state}: ProjectsContext, fieldsToUpdate: ProjectFields): Promise<void> {
|
||||||
await api.update(state.selectedProject.id, state.selectedProject.name, fieldsToUpdate.description);
|
const project = new ProjectFields(
|
||||||
|
state.selectedProject.name,
|
||||||
|
fieldsToUpdate.description,
|
||||||
|
state.selectedProject.id,
|
||||||
|
);
|
||||||
|
const limit = new ProjectLimits(
|
||||||
|
state.currentLimits.bandwidthLimit,
|
||||||
|
state.currentLimits.bandwidthUsed,
|
||||||
|
state.currentLimits.storageLimit,
|
||||||
|
state.currentLimits.storageUsed,
|
||||||
|
);
|
||||||
|
await api.update(state.selectedProject.id, project, limit);
|
||||||
|
|
||||||
commit(UPDATE_PROJECT_DESCRIPTION, fieldsToUpdate);
|
commit(UPDATE_PROJECT_DESCRIPTION, fieldsToUpdate);
|
||||||
},
|
},
|
||||||
[DELETE]: async function ({commit}: ProjectsContext, projectID: string): Promise<void> {
|
[UPDATE_STORAGE_LIMIT]: async function ({commit, state}: any, limitsToUpdate: ProjectLimits): Promise<void> {
|
||||||
|
const project = new ProjectFields(
|
||||||
|
state.selectedProject.name,
|
||||||
|
state.selectedProject.description,
|
||||||
|
state.selectedProject.id,
|
||||||
|
);
|
||||||
|
const limit = new ProjectLimits(
|
||||||
|
state.currentLimits.bandwidthLimit,
|
||||||
|
state.currentLimits.bandwidthUsed,
|
||||||
|
limitsToUpdate.storageLimit,
|
||||||
|
state.currentLimits.storageUsed,
|
||||||
|
);
|
||||||
|
await api.update(state.selectedProject.id, project, limit);
|
||||||
|
|
||||||
|
commit(UPDATE_PROJECT_STORAGE_LIMIT, limitsToUpdate);
|
||||||
|
},
|
||||||
|
[UPDATE_BANDWIDTH_LIMIT]: async function ({commit, state}: any, limitsToUpdate: ProjectLimits): Promise<void> {
|
||||||
|
const project = new ProjectFields(
|
||||||
|
state.selectedProject.name,
|
||||||
|
state.selectedProject.description,
|
||||||
|
state.selectedProject.id,
|
||||||
|
);
|
||||||
|
const limit = new ProjectLimits(
|
||||||
|
limitsToUpdate.bandwidthLimit,
|
||||||
|
state.currentLimits.bandwidthUsed,
|
||||||
|
state.currentLimits.storageLimit,
|
||||||
|
state.currentLimits.storageUsed,
|
||||||
|
);
|
||||||
|
await api.update(state.selectedProject.id, project, limit);
|
||||||
|
|
||||||
|
commit(UPDATE_PROJECT_BANDWIDTH_LIMIT, limitsToUpdate);
|
||||||
|
},
|
||||||
|
[DELETE]: async function ({commit}: any, projectID: string): Promise<void> {
|
||||||
await api.delete(projectID);
|
await api.delete(projectID);
|
||||||
|
|
||||||
commit(REMOVE, projectID);
|
commit(REMOVE, projectID);
|
||||||
|
@ -28,7 +28,7 @@ export interface ProjectsApi {
|
|||||||
* @returns Project[]
|
* @returns Project[]
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
update(projectId: string, name: string, description: string): Promise<void>;
|
update(projectId: string, updateProjectFields: ProjectFields, updateProjectLimits: ProjectLimits): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Delete project.
|
* Delete project.
|
||||||
*
|
*
|
||||||
|
@ -35,7 +35,7 @@ export class ProjectsApiMock implements ProjectsApi {
|
|||||||
return Promise.resolve(this.mockProjectsPage);
|
return Promise.resolve(this.mockProjectsPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(_projectId: string, _name: string, _description: string): Promise<void> {
|
update(_projectId: string, _projectFields: ProjectFields, _projectLimits: ProjectLimits): Promise<void> {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import Vuex from 'vuex';
|
|||||||
import EditProjectDetails from '@/components/project/EditProjectDetails.vue';
|
import EditProjectDetails from '@/components/project/EditProjectDetails.vue';
|
||||||
|
|
||||||
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
|
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
|
||||||
import { Project } from '@/types/projects';
|
import { Project, ProjectLimits } from '@/types/projects';
|
||||||
import { NotificatorPlugin } from '@/utils/plugins/notificator';
|
import { NotificatorPlugin } from '@/utils/plugins/notificator';
|
||||||
import { createLocalVue, mount } from '@vue/test-utils';
|
import { createLocalVue, mount } from '@vue/test-utils';
|
||||||
|
|
||||||
@ -18,7 +18,9 @@ const localVue = createLocalVue();
|
|||||||
localVue.use(Vuex);
|
localVue.use(Vuex);
|
||||||
localVue.use(notificationPlugin);
|
localVue.use(notificationPlugin);
|
||||||
|
|
||||||
|
const projectLimits = new ProjectLimits(1000, 100, 1000, 100);
|
||||||
const projectsApi = new ProjectsApiMock();
|
const projectsApi = new ProjectsApiMock();
|
||||||
|
projectsApi.setMockLimits(projectLimits);
|
||||||
const projectsModule = makeProjectsModule(projectsApi);
|
const projectsModule = makeProjectsModule(projectsApi);
|
||||||
|
|
||||||
const store = new Vuex.Store({ modules: { projectsModule }});
|
const store = new Vuex.Store({ modules: { projectsModule }});
|
||||||
@ -28,6 +30,7 @@ describe('EditProjectDetails.vue', () => {
|
|||||||
it('renders correctly', (): void => {
|
it('renders correctly', (): void => {
|
||||||
store.commit(PROJECTS_MUTATIONS.ADD, project);
|
store.commit(PROJECTS_MUTATIONS.ADD, project);
|
||||||
store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, project.id);
|
store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, project.id);
|
||||||
|
store.commit(PROJECTS_MUTATIONS.SET_LIMITS, projectLimits);
|
||||||
|
|
||||||
const wrapper = mount(EditProjectDetails, {
|
const wrapper = mount(EditProjectDetails, {
|
||||||
store,
|
store,
|
||||||
|
@ -17,6 +17,7 @@ exports[`EditProjectDetails.vue editing description works correctly 1`] = `
|
|||||||
<div class="project-details__wrapper__container__description-editing"><input placeholder="Enter a description for your project" class="project-details__wrapper__container__description-editing__input"> <span class="project-details__wrapper__container__description-editing__limit">4/100</span>
|
<div class="project-details__wrapper__container__description-editing"><input placeholder="Enter a description for your project" class="project-details__wrapper__container__description-editing__input"> <span class="project-details__wrapper__container__description-editing__limit">4/100</span>
|
||||||
<div class="project-details__wrapper__container__description-editing__save-button container" style="width: 66px; height: 30px; border-radius: 6px;"><span class="label">Save</span></div>
|
<div class="project-details__wrapper__container__description-editing__save-button container" style="width: 66px; height: 30px; border-radius: 6px;"><span class="label">Save</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -40,6 +41,7 @@ exports[`EditProjectDetails.vue editing description works correctly 2`] = `
|
|||||||
<div class="container white" style="width: 64px; height: 28px; border-radius: 6px;"><span class="label">Edit</span></div>
|
<div class="container white" style="width: 64px; height: 28px; border-radius: 6px;"><span class="label">Edit</span></div>
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -62,6 +64,7 @@ exports[`EditProjectDetails.vue editing name works correctly 1`] = `
|
|||||||
<div class="container white" style="width: 64px; height: 28px; border-radius: 6px;"><span class="label">Edit</span></div>
|
<div class="container white" style="width: 64px; height: 28px; border-radius: 6px;"><span class="label">Edit</span></div>
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -85,6 +88,7 @@ exports[`EditProjectDetails.vue editing name works correctly 2`] = `
|
|||||||
<div class="container white" style="width: 64px; height: 28px; border-radius: 6px;"><span class="label">Edit</span></div>
|
<div class="container white" style="width: 64px; height: 28px; border-radius: 6px;"><span class="label">Edit</span></div>
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -108,6 +112,7 @@ exports[`EditProjectDetails.vue renders correctly 1`] = `
|
|||||||
<div class="container white" style="width: 64px; height: 28px; border-radius: 6px;"><span class="label">Edit</span></div>
|
<div class="container white" style="width: 64px; height: 28px; border-radius: 6px;"><span class="label">Edit</span></div>
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user