Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Commit

Permalink
Merge pull request #808 from zanata/rhbz1082840-del-proj-ver
Browse files Browse the repository at this point in the history
Rhbz1082840 delete project or version
  • Loading branch information
Patrick Huang committed May 29, 2015
2 parents af0dc2d + cbe1676 commit 8ac9d99
Show file tree
Hide file tree
Showing 35 changed files with 343 additions and 208 deletions.
3 changes: 3 additions & 0 deletions docs/release-notes.md
Expand Up @@ -53,6 +53,9 @@ Example usage in html file: `<link rel="shortcut icon" href="#{assets['img/logo/
* [1198433](https://bugzilla.redhat.com/show_bug.cgi?id=1198433) - Replace Seam Text with CommonMark Markdown
* User text on the home page and project "about" pages will now be rendered as CommonMark.
* Existing Seam Text will be migrated to CommonMark where possible.
* [1204982](https://bugzilla.redhat.com/show_bug.cgi?id=1204982) - Documentation update for zanata.org/help + readthedocs
* [1211849](https://bugzilla.redhat.com/show_bug.cgi?id=1211849) - Project maintainer can change project/version slug
* [1082840](https://bugzilla.redhat.com/show_bug.cgi?id=1082840) - Project maintainer can delete a project or project version

## 3.6.3

Expand Down
3 changes: 2 additions & 1 deletion functional-test/src/main/java/org/zanata/page/CorePage.java
Expand Up @@ -90,9 +90,10 @@ public List<String> getErrors() {
WebElementUtil.elementsToText(getDriver(),
By.xpath("//span[@class='errors']"));

// app-error is a pseudo class we put in just for this
List<String> newError =
WebElementUtil.elementsToText(getDriver(),
By.className("message--danger"));
By.className("app-error"));

List<String> allErrors = Lists.newArrayList();
allErrors.addAll(oldError);
Expand Down
Expand Up @@ -43,7 +43,6 @@ public class ProjectsPage extends BasePage {
private By projectTable = By.id("main_content:form:projectList");
private By activeCheckBox = By.xpath("//*[@data-original-title='Filter active projects']");
private By readOnlyCheckBox = By.xpath("//*[@data-original-title='Filter read-only projects']");
private By archivedCheckBox = By.xpath("//*[@data-original-title='Filter archived projects']");

public ProjectsPage(final WebDriver driver) {
super(driver);
Expand Down Expand Up @@ -126,12 +125,4 @@ public ProjectsPage setReadOnlyFilterEnabled(final boolean enabled) {
return new ProjectsPage(getDriver());
}

public ProjectsPage setArchivedFilterEnabled(boolean enabled) {
log.info("Click to set Archived filter enabled to {}", enabled);
WebElement archivedCheckbox = readyElement(archivedCheckBox);
if (archivedCheckbox.isSelected() != enabled) {
archivedCheckbox.click();
}
return new ProjectsPage(getDriver());
}
}
Expand Up @@ -44,8 +44,10 @@ public class ProjectGeneralTab extends ProjectBasePage {
private By projectTypeList = By.id("project-types");
private By homepageField = By.id("settings-general-form:homePageField:homePage");
private By repoField = By.id("settings-general-form:repoField:repo");
private By archiveButton = By.id("settings-general-form:button-archive-project");
private By unarchiveButton = By.id("settings-general-form:button-unarchive-project");
private By deleteButton = By.id("button-archive-project");
private By confirmDeleteButton = By.id("deleteButton");
private By confirmDeleteInput = By.id("confirmDeleteInput");
private By cancelDeleteButton = By.id("cancelDelete");
private By lockProjectButton = By.id("settings-general-form:button-lock-project");
private By unlockProjectButton = By.id("settings-general-form:button-unlock-project");
private By updateButton = By.id("settings-general-form:button-update-settings");
Expand Down Expand Up @@ -157,28 +159,41 @@ private Map<String, WebElement> getProjectTypes() {
* Only Administrators can use this feature.
* @return button available true/false
*/
public boolean isArchiveButtonAvailable() {
public boolean isDeleteButtonAvailable() {
log.info("Query is Archive button displayed");
return getDriver().findElements(archiveButton).size() > 0;
return getDriver().findElements(deleteButton).size() > 0;
}

/**
* Press the "Archive this project" button
* @return new Project General Settings page
* Press the "Delete this project" button
* @return new Dashboard page
*/
public ProjectGeneralTab archiveProject() {
log.info("Click Archive this project");
clickElement(archiveButton);
public ProjectGeneralTab deleteProject() {
log.info("Click Delete this project");
clickElement(deleteButton);
return new ProjectGeneralTab(getDriver());
}

/**
* Press the "Unarchive this project" button
* @return new Project General Settings page
* Enter exact project name again to confirm the deletion.
* @param projectName project name
* @return this page
*/
public ProjectGeneralTab enterProjectNameToConfirmDelete(String projectName) {
log.info("Input project name again to confirm");
readyElement(confirmDeleteInput).clear();
readyElement(confirmDeleteInput).sendKeys(projectName);
waitForPageSilence();
return this;
}

/**
* Confirm project delete
* @return new Dashboard page
*/
public ProjectGeneralTab unarchiveProject() {
log.info("Click Unarchive this project");
clickElement(unarchiveButton);
public ProjectGeneralTab confirmDeleteProject() {
log.info("Click confirm Delete");
clickElement(confirmDeleteButton);
return new ProjectGeneralTab(getDriver());
}

Expand Down
Expand Up @@ -71,7 +71,6 @@ public void setAProjectToReadOnly() throws Exception {
.goToProjects()
.setActiveFilterEnabled(true)
.setReadOnlyFilterEnabled(false)
.setArchivedFilterEnabled(false)
.expectProjectNotVisible("about fedora");

assertThat(projectsPage.getProjectNamesOnCurrentPage())
Expand All @@ -80,7 +79,6 @@ public void setAProjectToReadOnly() throws Exception {

projectsPage = projectsPage.setActiveFilterEnabled(false)
.setReadOnlyFilterEnabled(true)
.setArchivedFilterEnabled(false)
.expectProjectVisible("about fedora");

assertThat(projectsPage.getProjectNamesOnCurrentPage())
Expand All @@ -102,7 +100,6 @@ public void setAProjectToWritable() throws Exception {
.setActiveFilterEnabled(false)
.setReadOnlyFilterEnabled(true)
.expectProjectVisible("about fedora")
.setArchivedFilterEnabled(false)
.getProjectNamesOnCurrentPage())
.contains("about fedora")
.as("The project is locked");
Expand All @@ -117,7 +114,6 @@ public void setAProjectToWritable() throws Exception {
.goToProjects()
.setActiveFilterEnabled(true)
.setReadOnlyFilterEnabled(false)
.setArchivedFilterEnabled(false)
.expectProjectVisible("about fedora");

assertThat(projectsPage.getProjectNamesOnCurrentPage())
Expand Down
Expand Up @@ -6,7 +6,6 @@
import org.zanata.feature.Feature;
import org.zanata.feature.testharness.ZanataTestCase;
import org.zanata.page.projects.ProjectsPage;
import org.zanata.page.projects.projectsettings.ProjectGeneralTab;
import org.zanata.util.AddUsersRule;
import org.zanata.util.SampleProjectRule;
import org.zanata.workflow.LoginWorkFlow;
Expand All @@ -24,26 +23,27 @@ public class SetProjectVisibilityTest extends ZanataTestCase {
@Rule
public SampleProjectRule sampleProjectRule = new SampleProjectRule();

@Feature(summary = "The administrator can set a project to archived",
@Feature(summary = "The administrator can delete a project",
tcmsTestPlanIds = 5316, tcmsTestCaseIds = 135846)
@Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION)
public void setAProjectArchived() throws Exception {
public void deleteAProject() throws Exception {
ProjectsPage projectsPage = new LoginWorkFlow()
.signIn("admin", "admin")
.goToProjects()
.goToProject("about fedora")
.gotoSettingsTab()
.gotoSettingsGeneral()
.archiveProject()
.deleteProject()
.enterProjectNameToConfirmDelete("about fedora")
.confirmDeleteProject()
.goToProjects();

assertThat(projectsPage.getProjectNamesOnCurrentPage())
.doesNotContain("about fedora")
.as("The project is not displayed");

projectsPage = projectsPage.setActiveFilterEnabled(false)
.setReadOnlyFilterEnabled(false)
.setArchivedFilterEnabled(true);
.setReadOnlyFilterEnabled(false);

projectsPage.expectProjectVisible("about fedora");

Expand All @@ -61,37 +61,4 @@ public void setAProjectArchived() throws Exception {
.as("User cannot navigate to the archived project");
}

@Feature(summary = "The administrator can set an archived project " +
"to active",
tcmsTestPlanIds = 5316, tcmsTestCaseIds = 0)
@Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION)
public void setAnArchivedProjectAsActive() throws Exception {
ProjectGeneralTab projectGeneralTab = new LoginWorkFlow()
.signIn("admin", "admin")
.goToProjects()
.goToProject("about fedora")
.gotoSettingsTab()
.gotoSettingsGeneral()
.archiveProject()
.goToProjects()
.setArchivedFilterEnabled(true)
.goToProject("about fedora")
.gotoSettingsTab()
.gotoSettingsGeneral()
.unarchiveProject();

assertThat(projectGeneralTab.isArchiveButtonAvailable())
.isTrue()
.as("The archive button is now available");

projectGeneralTab.logout();

assertThat(new LoginWorkFlow()
.signIn("translator", "translator")
.goToProjects()
.goToProject("about fedora")
.getProjectName())
.isEqualTo("about fedora")
.as("Translator can view the project");
}
}
Expand Up @@ -83,16 +83,18 @@ public void unsuccessfulProjectSearch() throws Exception {
.as("No projects are displayed");
}

@Feature(summary = "The user cannot search for Archived projects",
@Feature(summary = "The user cannot search for Deleted projects",
tcmsTestPlanIds = 5316, tcmsTestCaseIds = 0)
@Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION)
public void normalUserCannotSearchArchived() throws Exception {
public void userCannotSearchDeleteProject() throws Exception {
new LoginWorkFlow().signIn("admin", "admin")
.goToProjects()
.goToProject("about fedora")
.gotoSettingsTab()
.gotoSettingsGeneral()
.archiveProject()
.deleteProject()
.enterProjectNameToConfirmDelete("about fedora")
.confirmDeleteProject()
.logout();

BasePage basePage = new BasicWorkFlow()
Expand Down
13 changes: 10 additions & 3 deletions zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java
Expand Up @@ -395,9 +395,16 @@ public HTextFlowTargetReviewComment addReviewComment(String comment,

@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("contents", getContents())
.add("locale", getLocale()).add("state", getState())
.add("comment", getComment())
MoreObjects.ToStringHelper helper =
MoreObjects.toStringHelper(this)
.add("contents", getContents())
.add("locale", getLocale())
.add("state", getState())
.add("comment", getComment());
if (getTextFlow() == null) {
return helper.toString();
}
return helper
.add("textFlow", getTextFlow().getContents()).toString();
}

Expand Down
45 changes: 44 additions & 1 deletion zanata-model/src/main/java/org/zanata/model/SlugEntityBase.java
Expand Up @@ -20,9 +20,11 @@
*/
package org.zanata.model;

import java.util.Date;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

Expand All @@ -31,10 +33,12 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import org.hibernate.annotations.NaturalId;
import org.hibernate.search.annotations.Field;
import org.zanata.model.validator.Slug;
import com.google.common.annotations.VisibleForTesting;

@MappedSuperclass
@ToString(callSuper = true)
Expand All @@ -43,15 +47,54 @@
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public abstract class SlugEntityBase extends ModelEntityBase {

/**
* We append this suffix to a deleted slug entity so that its original slug
* become available to use.
*/
private static final String DELETED_SLUG_SUFFIX = "_.-";

private static final long serialVersionUID = -1911540675412928681L;

// TODO PERF @NaturalId(mutable=false) for better criteria caching
@NaturalId(mutable = true)
@Size(min = 1, max = 40)
@Slug
@NotNull
@Field
private String slug;

/**
* If the slug entity is set to obsolete (soft delete), we need to recycle
* its slug. This will suffix the original slug with deleted slug suffix
* plus a timestamp. if original slug is too long, the suffix will be put
* into the end and replacing some of the old slug characters. This means we
* won't be able to recover what the old slug was.
*
* @return an artificial slug just so the old slug will become available to
* use.
*/
@Transient
public String changeToDeletedSlug() {
String deletedSlugSuffix = deletedSlugSuffix();
String newSlug = slug + deletedSlugSuffix;
if (newSlug.length() <= 40) {
return newSlug;
} else {
newSlug =
slug.substring(0, 40 - deletedSlugSuffix.length())
+ deletedSlugSuffix;
log.warn(
"Entity [{}] old slug [{}] is too long to apply suffix. We will add suffix in place [{}]",
this, slug, newSlug);
return newSlug;
}
}

@VisibleForTesting
protected String deletedSlugSuffix() {
int timeSuffix = new Date().hashCode();
return DELETED_SLUG_SUFFIX + timeSuffix;
}
}
Expand Up @@ -21,6 +21,7 @@
package org.zanata.model;

import lombok.NoArgsConstructor;
import org.assertj.core.api.Assertions;
import org.junit.Test;

import static org.hamcrest.MatcherAssert.assertThat;
Expand All @@ -32,11 +33,19 @@
* href="mailto:pahuang@redhat.com">pahuang@redhat.com</a>
*/
public class SlugEntityBaseTest {

private static final String DELETED_SUFFIX = "_.-1234567890";

@NoArgsConstructor
static class SlugClass extends SlugEntityBase {
public SlugClass(String slug) {
super(slug);
}

@Override
protected String deletedSlugSuffix() {
return DELETED_SUFFIX;
}
}

@Test
Expand All @@ -59,4 +68,21 @@ public void lombokToStringAndEqualsTest() {
assertThat(entity.hashCode(), equalTo(other.hashCode()));

}

@Test
public void changeToDeletedSlug() {
SlugEntityBase slugEntityBase = new SlugClass("abc");
String newSlug = slugEntityBase.changeToDeletedSlug();
Assertions.assertThat(newSlug).isEqualTo("abc" + DELETED_SUFFIX);
}

@Test
public void canChangeToDeletedSlugWithSuffixInPlaceIfOldSlugIsTooLong() {
// 36 characters long
SlugEntityBase slugEntityBase = new SlugClass("abcdefghijklmnopqrstuvwxyz1234567890");
String newSlug = slugEntityBase.changeToDeletedSlug();
Assertions.assertThat(newSlug)
.isEqualTo("abcdefghijklmnopqrstuvwxyz1" + DELETED_SUFFIX)
.hasSize(40);
}
}

0 comments on commit 8ac9d99

Please sign in to comment.