diff --git a/api/pom.xml b/api/pom.xml index 6eefe6a39e..8c11bef3fa 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -8,7 +8,7 @@ org.zanata parent - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT ../parent diff --git a/api/zanata-common-api/pom.xml b/api/zanata-common-api/pom.xml index c563ef47af..e5e0e9d7b8 100644 --- a/api/zanata-common-api/pom.xml +++ b/api/zanata-common-api/pom.xml @@ -7,7 +7,7 @@ org.zanata api - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT jar diff --git a/build-tools/pom.xml b/build-tools/pom.xml index 3251310306..e84e5c3205 100644 --- a/build-tools/pom.xml +++ b/build-tools/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.zanata build-tools - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT Build Tools Build tools for Zanata modules http://zanata.org/ diff --git a/client/pom.xml b/client/pom.xml index 0154c0b2d1..eeffa7b6dd 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -7,7 +7,7 @@ org.zanata parent - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT ../parent diff --git a/client/stub-server/pom.xml b/client/stub-server/pom.xml index 7cf4465fe1..98068963b9 100644 --- a/client/stub-server/pom.xml +++ b/client/stub-server/pom.xml @@ -4,7 +4,7 @@ org.zanata client - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT stub-server stub-server diff --git a/client/zanata-cli/pom.xml b/client/zanata-cli/pom.xml index 9433e65264..9c1a922968 100644 --- a/client/zanata-cli/pom.xml +++ b/client/zanata-cli/pom.xml @@ -4,7 +4,7 @@ org.zanata client - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT zanata-cli Zanata command-line client diff --git a/client/zanata-cli/src/main/java/org/zanata/client/ZanataClient.java b/client/zanata-cli/src/main/java/org/zanata/client/ZanataClient.java index 7d37b6a8fa..8b017cf5b7 100644 --- a/client/zanata-cli/src/main/java/org/zanata/client/ZanataClient.java +++ b/client/zanata-cli/src/main/java/org/zanata/client/ZanataClient.java @@ -202,11 +202,19 @@ protected void processArgs(String... args) { * @param options */ private void copyGlobalOptionsTo(BasicOptions options) { - options.setDebug(getDebug()); - options.setErrors(getErrors()); + if (!options.isDebugSet()) { + options.setDebug(getDebug()); + } + if (!options.isErrorsSet()) { + options.setErrors(getErrors()); + } options.setHelp(getHelp()); - options.setInteractiveMode(isInteractiveMode()); - options.setQuiet(getQuiet()); + if (!options.isInteractiveModeSet()) { + options.setInteractiveMode(isInteractiveMode()); + } + if (!options.isQuietSet()) { + options.setQuiet(getQuiet()); + } } private void printHelp(PrintWriter out) { diff --git a/client/zanata-client-commands/pom.xml b/client/zanata-client-commands/pom.xml index 559bd5da7f..ccfbb6e240 100644 --- a/client/zanata-client-commands/pom.xml +++ b/client/zanata-client-commands/pom.xml @@ -4,7 +4,7 @@ org.zanata client - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT zanata-client-commands Zanata client commands diff --git a/client/zanata-client-commands/src/main/java/org/zanata/client/commands/BasicOptions.java b/client/zanata-client-commands/src/main/java/org/zanata/client/commands/BasicOptions.java index 3cf317efd3..ef7470be9b 100644 --- a/client/zanata-client-commands/src/main/java/org/zanata/client/commands/BasicOptions.java +++ b/client/zanata-client-commands/src/main/java/org/zanata/client/commands/BasicOptions.java @@ -53,6 +53,8 @@ public interface BasicOptions { void setInteractiveMode(boolean interactiveMode); + boolean isInteractiveModeSet(); + /** * Used to generate the command line interface and its usage help. This name * should match the Maven Mojo's 'goal' annotation and must match the @SubCommand diff --git a/client/zanata-client-commands/src/main/java/org/zanata/client/commands/BasicOptionsImpl.java b/client/zanata-client-commands/src/main/java/org/zanata/client/commands/BasicOptionsImpl.java index c02929332f..12d4ba38e0 100644 --- a/client/zanata-client-commands/src/main/java/org/zanata/client/commands/BasicOptionsImpl.java +++ b/client/zanata-client-commands/src/main/java/org/zanata/client/commands/BasicOptionsImpl.java @@ -42,6 +42,7 @@ public abstract class BasicOptionsImpl implements BasicOptions { private boolean quiet = false; private boolean quietSet; private boolean interactiveMode = true; + private boolean interactiveModeSet; private List commandHooks = new ArrayList(); public BasicOptionsImpl() { @@ -109,6 +110,7 @@ public boolean isInteractiveMode() { @Override public void setInteractiveMode(boolean interactiveMode) { + interactiveModeSet = true; this.interactiveMode = interactiveMode; } @@ -133,6 +135,11 @@ public boolean isQuietSet() { return quietSet; } + @Override + public boolean isInteractiveModeSet() { + return interactiveModeSet; + } + @Override public void setCommandHooks(List commandHooks) { this.commandHooks = commandHooks; diff --git a/client/zanata-client-commands/src/main/java/org/zanata/client/commands/OptionsUtil.java b/client/zanata-client-commands/src/main/java/org/zanata/client/commands/OptionsUtil.java index 72a3ce659d..ac6d4c606e 100644 --- a/client/zanata-client-commands/src/main/java/org/zanata/client/commands/OptionsUtil.java +++ b/client/zanata-client-commands/src/main/java/org/zanata/client/commands/OptionsUtil.java @@ -362,17 +362,25 @@ public static void applyUserConfig(ConfigurableOptions opts, if (quiet != null) opts.setQuiet(quiet); } + + if (!opts.isInteractiveModeSet()) { + Boolean batchMode = config.getBoolean("defaults.batchMode", null); + if (batchMode != null) + opts.setInteractiveMode(!batchMode); + } if ((opts.getUsername() == null || opts.getKey() == null) && opts.getUrl() != null) { SubnodeConfiguration servers = config.getSection("servers"); - String prefix = ConfigUtil.findPrefix(servers, opts.getUrl()); - if (prefix != null) { - if (opts.getUsername() == null) { - opts.setUsername(servers.getString(prefix + ".username", - null)); - } - if (opts.getKey() == null) { - opts.setKey(servers.getString(prefix + ".key", null)); + if (servers != null) { + String prefix = ConfigUtil.findPrefix(servers, opts.getUrl()); + if (prefix != null) { + if (opts.getUsername() == null) { + opts.setUsername(servers.getString(prefix + ".username", + null)); + } + if (opts.getKey() == null) { + opts.setKey(servers.getString(prefix + ".key", null)); + } } } } diff --git a/client/zanata-client-commands/src/main/java/org/zanata/client/commands/push/AbstractCommonPushStrategy.java b/client/zanata-client-commands/src/main/java/org/zanata/client/commands/push/AbstractCommonPushStrategy.java index 8d3bd0fb88..9478a5ee05 100644 --- a/client/zanata-client-commands/src/main/java/org/zanata/client/commands/push/AbstractCommonPushStrategy.java +++ b/client/zanata-client-commands/src/main/java/org/zanata/client/commands/push/AbstractCommonPushStrategy.java @@ -61,6 +61,11 @@ public String[] getSrcFiles(File srcDir, ImmutableList includes, includes = builder.build(); } + ImmutableList.Builder emptyFileExcludesBuilder = ImmutableList.builder(); + for (String fileExtension : fileExtensions) { + emptyFileExcludesBuilder.add("**/" + fileExtension); + } + DirectoryScanner dirScanner = new DirectoryScanner(); if (useDefaultExcludes) { @@ -71,7 +76,11 @@ public String[] getSrcFiles(File srcDir, ImmutableList includes, dirScanner.setCaseSensitive(isCaseSensitive); - dirScanner.setExcludes(excludes.toArray(new String[excludes.size()])); + emptyFileExcludesBuilder.addAll(excludes); + ImmutableList allExcludes = emptyFileExcludesBuilder.build(); + + + dirScanner.setExcludes(allExcludes.toArray(new String[allExcludes.size()])); dirScanner.setIncludes(includes.toArray(new String[includes.size()])); dirScanner.scan(); String[] includedFiles = dirScanner.getIncludedFiles(); diff --git a/client/zanata-client-commands/src/test/java/org/zanata/client/commands/OptionsUtilTest.java b/client/zanata-client-commands/src/test/java/org/zanata/client/commands/OptionsUtilTest.java index 6f6c3bb986..f316843f1c 100644 --- a/client/zanata-client-commands/src/test/java/org/zanata/client/commands/OptionsUtilTest.java +++ b/client/zanata-client-commands/src/test/java/org/zanata/client/commands/OptionsUtilTest.java @@ -2,24 +2,29 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.zanata.client.commands.ConsoleInteractor.DisplayMode.Question; import static org.zanata.client.commands.ConsoleInteractor.DisplayMode.Warning; import static org.zanata.client.commands.FileMappingRuleHandler.Placeholders.allHolders; import static org.zanata.client.commands.Messages.get; import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.file.Files; import java.util.List; import java.util.Optional; import javax.xml.bind.JAXBException; +import org.apache.commons.configuration.HierarchicalINIConfiguration; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.zanata.client.config.FileMappingRule; import org.zanata.client.config.LocaleList; @@ -110,6 +115,36 @@ public void optionTakesPrecedenceOverConfig() { assertThat(opts.getExcludes()).contains("a.properties", "b.properties"); } + @Test + public void applyUserConfigTestDefault() throws MalformedURLException { + opts.setUsername("username"); + opts.setUrl(new URL("http://localhost")); + + HierarchicalINIConfiguration config = + Mockito.mock(HierarchicalINIConfiguration.class); + OptionsUtil.applyUserConfig(opts, config); + + verify(config).getSection("servers"); + verify(config).getBoolean("defaults.debug", null); + verify(config).getBoolean("defaults.errors", null); + verify(config).getBoolean("defaults.quiet", null); + verify(config).getBoolean("defaults.batchMode", null); + } + + @Test + public void applyUserConfigTest() { + opts.setDebug(false); + opts.setErrors(false); + opts.setQuiet(false); + opts.setInteractiveMode(false); + + HierarchicalINIConfiguration config = + Mockito.mock(HierarchicalINIConfiguration.class); + OptionsUtil.applyUserConfig(opts, config); + + verifyZeroInteractions(config); + } + @Test public void willWarnUserIfRuleSeemsWrong() { opts.setInteractiveMode(false); diff --git a/client/zanata-client-commands/src/test/java/org/zanata/client/commands/push/XmlStrategyTest.java b/client/zanata-client-commands/src/test/java/org/zanata/client/commands/push/XmlStrategyTest.java index d7dc6a2f4a..4d2d17cbc8 100644 --- a/client/zanata-client-commands/src/test/java/org/zanata/client/commands/push/XmlStrategyTest.java +++ b/client/zanata-client-commands/src/test/java/org/zanata/client/commands/push/XmlStrategyTest.java @@ -23,17 +23,22 @@ import java.io.File; import java.io.IOException; +import java.util.Set; import javax.xml.bind.Unmarshaller; +import com.google.common.collect.ImmutableList; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.zanata.client.TempTransFileRule; +import org.zanata.client.TestUtils; import org.zanata.client.config.FileMappingRule; import org.zanata.client.config.LocaleList; import org.zanata.client.config.LocaleMapping; @@ -47,6 +52,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import static org.zanata.client.TestUtils.createAndAddLocaleMapping; public class XmlStrategyTest { @@ -77,6 +83,33 @@ public void setUp() throws IOException { sourceResource = new Resource("test"); } + @Test + public void findDocNamesTest() throws IOException { + PushOptions mockPushOption = Mockito.mock(PushOptions.class); + File sourceDir = TestUtils.fileFromClasspath("xliffDir"); + LocaleList locales = new LocaleList(); + String sourceLocale = "en-US"; + + ImmutableList include = ImmutableList.of(); + ImmutableList exclude = ImmutableList.of(); + + when(mockPushOption.getLocaleMapList()).thenReturn(locales); + when(mockPushOption.getSourceLang()).thenReturn(sourceLocale); + when(mockPushOption.getDefaultExcludes()).thenReturn(true); + when(mockPushOption.getCaseSensitive()).thenReturn(true); + when(mockPushOption.getExcludeLocaleFilenames()).thenReturn(true); + when(mockPushOption.getValidate()).thenReturn("xsd"); + + strategy.setPushOptions(mockPushOption); + + Set localDocNames = + strategy.findDocNames(sourceDir, include, exclude, + mockPushOption.getDefaultExcludes(), + mockPushOption.getCaseSensitive(), + mockPushOption.getExcludeLocaleFilenames()); + Assert.assertEquals(7, localDocNames.size()); + } + @Test public void canVisitTranslationFileWithoutFileMapping() throws Exception { // with translation diff --git a/client/zanata-client-commands/src/test/resources/xliffDir/dir1/.xml b/client/zanata-client-commands/src/test/resources/xliffDir/dir1/.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/zanata-maven-plugin/pom.xml b/client/zanata-maven-plugin/pom.xml index b58ffe6d8e..9a36bf2db1 100644 --- a/client/zanata-maven-plugin/pom.xml +++ b/client/zanata-maven-plugin/pom.xml @@ -4,7 +4,7 @@ org.zanata client - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT zanata-maven-plugin diff --git a/client/zanata-maven-plugin/src/main/java/org/zanata/maven/ConfigurableMojo.java b/client/zanata-maven-plugin/src/main/java/org/zanata/maven/ConfigurableMojo.java index 79dc13f52a..16128182a9 100644 --- a/client/zanata-maven-plugin/src/main/java/org/zanata/maven/ConfigurableMojo.java +++ b/client/zanata-maven-plugin/src/main/java/org/zanata/maven/ConfigurableMojo.java @@ -60,6 +60,8 @@ public abstract class ConfigurableMojo extends @Parameter(defaultValue = "${settings.interactiveMode}") private boolean interactiveMode = true; + private boolean interactiveModeSet; + /** * Enable HTTP message logging. */ @@ -197,6 +199,11 @@ public boolean isQuietSet() { return !getLog().isInfoEnabled(); } + @Override + public boolean isInteractiveModeSet() { + return interactiveModeSet; + } + @Override public boolean isInteractiveMode() { return interactiveMode; @@ -204,6 +211,7 @@ public boolean isInteractiveMode() { @Override public void setInteractiveMode(boolean interactiveMode) { + interactiveModeSet = true; this.interactiveMode = interactiveMode; } diff --git a/client/zanata-rest-client/pom.xml b/client/zanata-rest-client/pom.xml index fba6225596..593ff99b9e 100644 --- a/client/zanata-rest-client/pom.xml +++ b/client/zanata-rest-client/pom.xml @@ -6,7 +6,7 @@ org.zanata client - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT jar diff --git a/common/pom.xml b/common/pom.xml index 459b40989c..684b6bdee0 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -7,7 +7,7 @@ org.zanata parent - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT ../parent diff --git a/common/zanata-adapter-glossary/pom.xml b/common/zanata-adapter-glossary/pom.xml index d60f77821a..ac884b4fb1 100644 --- a/common/zanata-adapter-glossary/pom.xml +++ b/common/zanata-adapter-glossary/pom.xml @@ -6,7 +6,7 @@ org.zanata common - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT jar diff --git a/common/zanata-adapter-glossary/src/main/java/org/zanata/adapter/glossary/GlossaryCSVReader.java b/common/zanata-adapter-glossary/src/main/java/org/zanata/adapter/glossary/GlossaryCSVReader.java index 558ecaa7c6..031109718b 100644 --- a/common/zanata-adapter-glossary/src/main/java/org/zanata/adapter/glossary/GlossaryCSVReader.java +++ b/common/zanata-adapter-glossary/src/main/java/org/zanata/adapter/glossary/GlossaryCSVReader.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.Reader; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -45,9 +46,15 @@ * **/ public class GlossaryCSVReader extends AbstractGlossaryPushReader { + private final LocaleId srcLang; private final static String POS = "POS"; + private final static String[] POSSYNONYMS = + {"POS", "PARTOFSPEECH", "PART OF SPEECH"}; private final static String DESC = "DESCRIPTION"; + private final static String[] DESCSYNONYMS = + {"DESCRIPTION", "DESC", "DEFINITION"}; + private final static int TERM = 0; /** * This class will close the reader @@ -68,7 +75,7 @@ public Map> extractGlossary(Reader reader, Map localeColMap = setupLocalesMap(records, descriptionMap); - LocaleId srcLocale = localeColMap.get(0); + LocaleId srcLocale = localeColMap.get(TERM); if (!srcLang.equals(srcLocale)) { throw new RuntimeException("input source language '" + srcLang @@ -77,20 +84,23 @@ public Map> extractGlossary(Reader reader, } Map> results = Maps.newHashMap(); - for (int i = 1; i < records.size(); i++) { - CSVRecord row = records.get(i); - for (int x = 1; x < row.size() - && localeColMap.containsKey(x); x++) { - - GlossaryEntry entry = new GlossaryEntry(); - entry.setSrcLang(srcLocale); + for (CSVRecord row : records.subList(1, records.size())) { + GlossaryEntry entry = new GlossaryEntry(); + entry.setSrcLang(srcLocale); + if (descriptionMap.containsKey(POS)) { entry.setPos(row.get(descriptionMap.get(POS))); + } + if (descriptionMap.containsKey(DESC)) { entry.setDescription(row.get(descriptionMap.get(DESC))); - entry.setQualifiedName(new QualifiedName(qualifiedName)); + } + entry.setQualifiedName(new QualifiedName(qualifiedName)); + + for (int x = 1; x < row.size() + && localeColMap.containsKey(x); x++) { GlossaryTerm srcTerm = new GlossaryTerm(); srcTerm.setLocale(srcLocale); - srcTerm.setContent(row.get(0)); + srcTerm.setContent(row.get(TERM)); entry.getGlossaryTerms().add(srcTerm); @@ -100,8 +110,8 @@ public Map> extractGlossary(Reader reader, GlossaryTerm transTerm = new GlossaryTerm(); transTerm.setLocale(transLocaleId); transTerm.setContent(transContent); - entry.getGlossaryTerms().add(transTerm); + List list = results.get(transLocaleId); if (list == null) { list = Lists.newArrayList(); @@ -117,8 +127,9 @@ public Map> extractGlossary(Reader reader, } /** - * Basic validation of CVS file format - At least 2 rows in the CVS file - - * Empty content validation - All row must have the same column count + * Basic validation of CSV file format - At least 2 rows in the CSV file - + * Empty content validation - All rows must have the same column count. + * @param records list of records representing a csv file */ private void validateCSVEntries(@Nonnull List records) { if (records.isEmpty()) { @@ -138,33 +149,46 @@ private void validateCSVEntries(@Nonnull List records) { } /** - * Parser reads from all from first row and exclude column from description - * map. Format of CVS: {source locale},{locale},{locale}...,pos,description + * Parser reads from all from first row and up to first recognised + * non-locale column mapping. + * Format of CSV: {source locale},{locale},{locale}...,pos,description + * @param records list of records representing a csv file + * @param descriptionMap header locations of non-locale columns */ private Map setupLocalesMap(List records, Map descriptionMap) { - Map localeColMap = new HashMap(); + Map localeColMap = new HashMap<>(); CSVRecord headerRow = records.get(0); - for (int row = 0; row <= headerRow.size() - && !descriptionMap.containsValue(row); row++) { + for (int column = 0; column < headerRow.size() + && !descriptionMap.containsValue(column); column++) { LocaleId locale = - new LocaleId(StringUtils.trim(headerRow.get(row))); - localeColMap.put(row, locale); + new LocaleId(StringUtils.trim(headerRow.get(column))); + localeColMap.put(column, locale); } return localeColMap; } /** - * Read last 2 columns in CSV: - * {source locale},{locale},{locale}...,pos,description + * Locate trailing data columns in CSV: + * {source locale},{locale},{locale}...,pos,description,... * - * @param records + * @param records list of records representing a csv file */ private Map setupDescMap(List records) { - Map descMap = new HashMap(); + Map descMap = new HashMap<>(); CSVRecord headerRow = records.get(0); - descMap.put(POS, headerRow.size() - 2); - descMap.put(DESC, headerRow.size() - 1); + for (int position = 1; position < headerRow.size(); position++) { + String headerItem = headerRow.get(position); + if (headerItem.trim().length() == 0) { + continue; + } + if (Arrays.asList(POSSYNONYMS).contains(headerItem.toUpperCase())) { + descMap.put(POS, position); + } else if (Arrays.asList(DESCSYNONYMS) + .contains(headerItem.toUpperCase())) { + descMap.put(DESC, position); + } + } return descMap; } } diff --git a/common/zanata-adapter-glossary/src/test/java/org/zanata/adapter/glossary/GlossaryCSVReaderTest.java b/common/zanata-adapter-glossary/src/test/java/org/zanata/adapter/glossary/GlossaryCSVReaderTest.java index 8839268b20..5acae7a90a 100644 --- a/common/zanata-adapter-glossary/src/test/java/org/zanata/adapter/glossary/GlossaryCSVReaderTest.java +++ b/common/zanata-adapter-glossary/src/test/java/org/zanata/adapter/glossary/GlossaryCSVReaderTest.java @@ -57,20 +57,18 @@ public void testNotMatchingSource() throws IOException { } @Test - public void extractGlossaryTest1() throws IOException { - + public void extractGlossary() throws IOException { GlossaryCSVReader reader = new GlossaryCSVReader(LocaleId.EN_US); int entryPerLocale = 2; File sourceFile = new File("src/test/resources/glossary/translate1.csv"); - Reader inputStreamReader = - new InputStreamReader(new FileInputStream(sourceFile), "UTF-8"); - BufferedReader br = new BufferedReader(inputStreamReader); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(new FileInputStream(sourceFile), "UTF-8")); Map> glossaries = - reader.extractGlossary(br, + reader.extractGlossary(bufferedReader, GlossaryResource.GLOBAL_QUALIFIED_NAME); assertThat(glossaries.keySet()).hasSize(2); @@ -79,29 +77,40 @@ public void extractGlossaryTest1() throws IOException { for (Map.Entry> entry : glossaries .entrySet()) { assertThat(entry.getValue()).hasSize(entryPerLocale); + assertEntry(entry); } } @Test - public void extractGlossaryTest2() throws IOException { + public void extractGlossaryWithOtherHeaders() throws IOException { GlossaryCSVReader reader = new GlossaryCSVReader(LocaleId.EN_US); int entryPerLocale = 2; File sourceFile = new File("src/test/resources/glossary/translate2.csv"); - Reader inputStreamReader = - new InputStreamReader(new FileInputStream(sourceFile), "UTF-8"); - BufferedReader br = new BufferedReader(inputStreamReader); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(new FileInputStream(sourceFile), "UTF-8")); - Map> glossaries = - reader.extractGlossary(br, GlossaryResource.GLOBAL_QUALIFIED_NAME); + Map> glossaries = reader.extractGlossary( + bufferedReader, GlossaryResource.GLOBAL_QUALIFIED_NAME); assertThat(glossaries.keySet()).hasSize(2); assertThat(glossaries.keySet()).contains(LocaleId.ES, new LocaleId("zh")); + for (Map.Entry> entry : glossaries .entrySet()) { assertThat(entry.getValue()).hasSize(entryPerLocale); + assertEntry(entry); } } + + private void assertEntry(Map.Entry> entry) { + assertThat(entry.getValue().get(0).getDescription() + .startsWith("testing of hello")); + assertThat(entry.getValue().get(1).getDescription() + .startsWith("testing of morning")); + assertThat(entry.getValue().get(0).getPos()).isEqualTo("verb"); + assertThat(entry.getValue().get(1).getPos()).isEqualTo("noun"); + } } diff --git a/common/zanata-adapter-glossary/src/test/resources/glossary/translate1.csv b/common/zanata-adapter-glossary/src/test/resources/glossary/translate1.csv index 133b626ac9..4d3d63f2bc 100644 --- a/common/zanata-adapter-glossary/src/test/resources/glossary/translate1.csv +++ b/common/zanata-adapter-glossary/src/test/resources/glossary/translate1.csv @@ -1,3 +1,3 @@ "en-US","es","zh","pos","description" -"Hello","Hola","您好","noun","testing of hello csv" +"Hello","Hola","您好","verb","testing of hello csv" "Morning","mañana","上午","noun","testing of morning csv" diff --git a/common/zanata-adapter-glossary/src/test/resources/glossary/translate2.csv b/common/zanata-adapter-glossary/src/test/resources/glossary/translate2.csv index 265175cf36..bcf16fa311 100644 --- a/common/zanata-adapter-glossary/src/test/resources/glossary/translate2.csv +++ b/common/zanata-adapter-glossary/src/test/resources/glossary/translate2.csv @@ -1,3 +1,3 @@ -"en-US","es","zh","description1","description2" -"Hello","Hola","您好","testing of hello csv1","testing of hello csv2" -"Morning","mañana","上午","testing of morning csv1","testing of morning csv2" +"en-US","es","zh","definition","synonyms","partofspeech" +"Hello","Hola","您好","testing of hello csv","Hi","verb" +"Morning","mañana","上午","testing of morning csv","Dawn,AM","noun" diff --git a/common/zanata-adapter-po/pom.xml b/common/zanata-adapter-po/pom.xml index 3eb94f51d3..62f638bb96 100644 --- a/common/zanata-adapter-po/pom.xml +++ b/common/zanata-adapter-po/pom.xml @@ -6,7 +6,7 @@ org.zanata common - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT jar diff --git a/common/zanata-adapter-properties/pom.xml b/common/zanata-adapter-properties/pom.xml index 9929c0e4df..a80f6e76c2 100644 --- a/common/zanata-adapter-properties/pom.xml +++ b/common/zanata-adapter-properties/pom.xml @@ -6,7 +6,7 @@ org.zanata common - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT jar diff --git a/common/zanata-adapter-xliff/pom.xml b/common/zanata-adapter-xliff/pom.xml index 26dd3b38ca..c8276f6102 100644 --- a/common/zanata-adapter-xliff/pom.xml +++ b/common/zanata-adapter-xliff/pom.xml @@ -6,7 +6,7 @@ org.zanata common - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT jar diff --git a/common/zanata-common-util/pom.xml b/common/zanata-common-util/pom.xml index f72cf9206b..0926566d6f 100644 --- a/common/zanata-common-util/pom.xml +++ b/common/zanata-common-util/pom.xml @@ -6,7 +6,7 @@ org.zanata common - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT jar diff --git a/docs/client/installation/linux-installation.md b/docs/client/installation/linux-installation.md index 9438afeba4..921461759a 100644 --- a/docs/client/installation/linux-installation.md +++ b/docs/client/installation/linux-installation.md @@ -1,175 +1,190 @@ ## RHEL 7 -There are two ways to install zanata-client, via `0install` or `yum`. +There are two ways to install zanata-cli, via `0install` or `yum`. ### With 0install - -**Note: If you have previously installed `zanata-cli` through 0install, please run this command to update your `zanata-cli`** - - 0install destroy zanata-cli | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml - -* To install a specific version (e.g. 4.1.1) - - 0install destroy zanata-cli | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml --version=4.1.1 - - **Note: If you have previously installed `zanata-cli` with Ivy or yum, you need to uninstall it first** ---- + 0. If you have not install 0install, or not sure, run following commands to install 0install +and other dependencies: -1. Install EPEL repository for RHEL 7 + sudo rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + sudo yum -y install 0install java-1.8.0-openjdk unzip - sudo rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + 1. To install zanata-cli, run: -2. Install `0install` + mkdir -p ~/bin + 0install destroy zanata-cli + yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 0install -c update zanata-cli - sudo yum -y install 0install java-1.8.0-openjdk unzip + 2. It should be done now. Run `zanata-cli --help` for the usage of the client. -3. Use `zanata-cli` as alias: + 3. To update zanata-cli, run: - mkdir -p ~/bin | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 0install -c update zanata-cli -4. It should be done now. Run `zanata-cli --help` for the usage of the client. - +--- ### With yum - **Note: If you have previously installed `zanata-cli` with Ivy or 0install, you need to uninstall it first** ---- +This method uses a 3rd-party yum repository, `Zanata_Team el-zanata` (a.k.a. dchen's epel-zanata). -1. Install `epel-zanata` repo + 0. To install `Zanata_Team el-zanata`, run: - cd /etc/yum.repos.d ; sudo wget https://repos.fedorapeople.org/dchen/zanata/epel-zanata.repo + sudo curl -L -o /etc/yum.repos.d/el-zanata.repo https://repos.fedorapeople.org/Zanata_Team/zanata/el-zanata.repo -2. Install package `zanata-cli-bin` + 1. To install zanata-cli, run: - sudo yum -y install zanata-cli-bin java-1.8.0-openjdk + sudo yum -y install zanata-cli-bin -3. It should be done now. Run `zanata-cli --help` for the usage of the client. + 2. It should be done now. Run `zanata-cli --help` for the usage of the client. + + 3. To update zanata-cli, run: + + sudo yum -y update zanata-cli-bin ## RHEL 6 There are two ways to install zanata-client, via `0install` or `yum`. ### With 0install -**Note: If you have previously installed `zanata-cli` through 0install, please run this command to update your `zanata-cli`** +**Note: If you have previously installed `zanata-cli` with Ivy or yum, you need to uninstall it first** - 0install destroy zanata-cli | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml -**Note: If you have previously installed `zanata-cli` with Ivy or yum, you need to uninstall it first** + 0. If you have not install 0install, or not sure, run following commands to install 0install +and other dependencies: + a. Install other dependencies: ---- + sudo yum -y install libcurl java-1.8.0-openjdk unzip -1. Download and extract `0install` binary package - a. for 64-bit machine (such as x86_64) + b. Download and extract 0install for 64-bit machine (such as x86_64) cd /tmp; wget https://downloads.sourceforge.net/project/zero-install/0install/2.10/0install-linux-x86_64-2.10.tar.bz2 tar xjvf 0install-linux-x86_64-2.10.tar.bz2 cd 0install-linux-x86_64-2.10 - - b. for 32-bit machine (such as i486) - + + c. Download and extract 0install for 32-bit machine (such as i486) + cd /tmp; wget https://downloads.sourceforge.net/project/zero-install/0install/2.10/0install-linux-i486-2.10.tar.bz2 tar xjvf 0install-linux-i486-2.10.tar.bz2 cd 0install-linux-i486-2.10 -2. Install `libcurl` if you have not + d. Install `0install` - sudo yum -y install libcurl java-1.8.0-openjdk unzip + ./install.sh -3. Install `0install` + 1. To install zanata-cli, run: - ./install.sh + mkdir -p ~/bin + 0install destroy zanata-cli + yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 0install -c update zanata-cli -4. Use `zanata-cli` as alias: + 2. It should be done now. Run `zanata-cli --help` for the usage of the client. - mkdir -p ~/bin | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 3. To update zanata-cli, run: -5. It should be done now. Run `zanata-cli --help` for the usage of the client. + 0install -c update zanata-cli +--- ### With yum - **Note: If you have previously installed `zanata-cli` with Ivy or 0install, you need to uninstall it first** ---- + 0. To install `Zanata_Team el-zanata`, run: + + sudo curl -L -o /etc/yum.repos.d/el-zanata.repo https://repos.fedorapeople.org/Zanata_Team/zanata/el-zanata.repo -1. Install `epel-zanata` repo + 1. To install zanata-cli, run: - cd /etc/yum.repos.d ; sudo wget https://repos.fedorapeople.org/dchen/zanata/epel-zanata.repo + sudo yum -y install zanata-cli-bin -2. Install package `zanata-cli-bin` + 2. It should be done now. Run `zanata-cli --help` for the usage of the client. - sudo yum -y install zanata-cli-bin java-1.8.0-openjdk + 3. To update zanata-cli, run: + + sudo yum -y update zanata-cli-bin -3. It should be done now. Run `zanata-cli --help` for the usage of the client. ## Fedora There are two ways to install zanata-client, via `0install` or `dnf`. ### With 0install +**Note: If you have previously installed `zanata-cli` with Ivy or yum, you need to uninstall it first** -**Note: If you have previously installed `zanata-cli` through 0install, please run this command to update your `zanata-cli`** - - 0install destroy zanata-cli | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml - -**Note: If you have previously installed `zanata-cli` with Ivy or dnf, you need to uninstall it first** + 0. If you have not install 0install, or not sure, run following commands to install 0install +and other dependencies: ---- + sudo yum -y install 0install java-1.8.0-openjdk unzip -1. Install 0install + 1. To install zanata-cli, run: - sudo yum -y install 0install java-1.8.0-openjdk unzip + mkdir -p ~/bin + 0install destroy zanata-cli + yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 0install -c update zanata-cli -2. Use `zanata-cli` as alias: + 2. It should be done now. Run `zanata-cli --help` for the usage of the client. - mkdir -p ~/bin | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 3. To update zanata-cli, run: -3. It should be done now. Run `zanata-cli --help` for the usage of the client. + 0install -c update zanata-cli +--- ### With dnf -**Note: If you have previously installed `zanata-cli` with Ivy or 0install, you need to uninstall it first** +`zanata-client`, the package that contains `zanata-cli`, is already in official Fedora repository. ---- +**Note: If you have previously installed `zanata-cli` with Ivy or 0install, you need to uninstall it first** -1. Install `zanata-client` + 1. To install `zanata-cli`, run: sudo dnf -y install zanata-client -## Debian based distro + 2. It should be done now. Run `zanata-cli --help` for the usage of the client. -**Note: If you have previously installed `zanata-cli` through 0install, please run this command to update your `zanata-cli`** + 3. To update zanata-cli, run: - 0install destroy zanata-cli | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + sudo dnf -y update zanata-client -**Note: If you have previously installed `zanata-cli` with Ivy or dnf, you need to uninstall it first** ---- +## Debian Based Distributions -1. Install 0install + 0. To install 0install and other dependencies, run: - sudo apt-get install zeroinstall-injector + sudo apt-get install zeroinstall-injector openjdk-8-jre -2. Install Java runtime: + 1. To install zanata-cli, run: - sudo apt-get install openjdk-8-jre + mkdir -p ~/bin + 0install destroy zanata-cli + yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 0install -c update zanata-cli -3. Use `zanata-cli` as alias: + 2. It should be done now. Run `zanata-cli --help` for the usage of the client. - mkdir -p ~/bin | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 3. To update zanata-cli, run: + + 0install -c update zanata-cli -4. It should be done now. Run `zanata-cli --help` for the usage of the client. ## Others *Note: If you have installed `zanata-cli` previously through another method, you need to uninstall that for this to work.* -1. Follow 0Install in [0Install for Linux](http://0install.net/install-linux.html). -2. Install Java JRE (1.8 onwards) from [OpenJDK installation](http://openjdk.java.net/install/index.html) -3. Use `zanata-cli` as alias: + 0. Follow 0Install in [0Install for Linux](http://0install.net/install-linux.html). + 1. Install Java JRE (1.8 onwards) from [OpenJDK installation](http://openjdk.java.net/install/index.html) + 2. To install zanata-cli, run: + + mkdir -p ~/bin + 0install destroy zanata-cli + yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 0install -c update zanata-cli + + 3. It should be done now. Run `zanata-cli --help` for the usage of the client. - mkdir -p ~/bin | yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 4. To update zanata-cli, run: -4. It should be done now. Run `zanata-cli --help` for the usage of the client. + 0install -c update zanata-cli ## 0install Useful commands @@ -178,6 +193,44 @@ There are two ways to install zanata-client, via `0install` or `dnf`. 0launch https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml {command} * Update Zanata-CLI manually - + 0install update zanata-cli + * Set version of zanata-cli to 4.4.0 + + 0install -c update zanata-cli --version=4.4.0 + +## Troubleshooting: Fedora dnf + +#### The latest package is not in repository +It may still in updates-testing repository. Run: + + sudo dnf -y --enablerepo=updates-testing update zanata-client + +## Troubleshooting: RHEL yum + +#### The latest package is not in repository +You local yum cache might not be updated. To refresh local yum cache, +run following command before you try again + + sudo yum makecache + +## Troubleshooting: 0install +#### 0launch URL works, but `zanata-cli` does not +Your `~/bin` might not be in path. Run: + + grep $HOME/bin<<<$PATH || echo "$HOME/bin is not in PATH" + +If `~/bin` is not in the path, then add following in both `~/.bashrc` and `~/.bash_profile` + + PATH+=":$HOME/bin" + + +#### Downloaded package does not work +Your feed may be broken, to fix it, run: + + mkdir -p ~/bin + 0install destroy zanata-cli + yes | 0install -c add zanata-cli https://raw.githubusercontent.com/zanata/zanata.github.io/master/files/0install/zanata-cli.xml + 0install -c update zanata-cli + diff --git a/docs/release-notes.md b/docs/release-notes.md index 5c25e7e3b2..ed8e2cbabd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,16 @@ +## 4.4.2 +##### Bug Fixes + * [ZNTA-2375](https://zanata.atlassian.net/browse/ZNTA-2375) - Single project is shown multiple times on explore page. + * [ZNTA-2372](https://zanata.atlassian.net/browse/ZNTA-2372) - 400 bad request on TM merge Translations for poject version without document. + +----------------------- + +## 4.4.1 +##### Bug Fixes + * [ZNTA-2374](https://zanata.atlassian.net/browse/ZNTA-2374) - SSO login with existing account only works on the second time + +----------------------- + ## 4.4.0 ##### Infrastructure Changes Zanata now requires a system property 'zanata.home' as the root for all its file system storage needs. @@ -5,6 +18,58 @@ The property 'zanata.file.directory' is still supported, but if it is missing, a ##### Changes * [ZNTA-2184](https://zanata.atlassian.net/browse/ZNTA-2225) - Define zanata.home system property and derive other properties from it + * [ZNTA-2326](https://zanata.atlassian.net/browse/ZNTA-2326) - Remove overlay + * [ZNTA-2308](https://zanata.atlassian.net/browse/ZNTA-2308) - Expand contents of frontend-storybook + * [ZNTA-2293](https://zanata.atlassian.net/browse/ZNTA-2293) - Changes to project version sidebar design + * [ZNTA-2280](https://zanata.atlassian.net/browse/ZNTA-2280) - Add descriptions to all waitFor instances in FT + * [ZNTA-2252](https://zanata.atlassian.net/browse/ZNTA-2252) - Prep for frontend/editor css merge + * [ZNTA-2233](https://zanata.atlassian.net/browse/ZNTA-2233) - Add more linting rules to stylelintrc + * [ZNTA-2231](https://zanata.atlassian.net/browse/ZNTA-2231) - Add a css linter to frontend + * [ZNTA-2230](https://zanata.atlassian.net/browse/ZNTA-2230) - Improve frontend caching + * [ZNTA-2225](https://zanata.atlassian.net/browse/ZNTA-2225) - Define zanata.home system property and derive other properties from it + * [ZNTA-2224](https://zanata.atlassian.net/browse/ZNTA-2224) - Tidy up editor css + * [ZNTA-2219](https://zanata.atlassian.net/browse/ZNTA-2219) - Nice-ify the glossary count in alpha editor + * [ZNTA-2216](https://zanata.atlassian.net/browse/ZNTA-2216) - Admin page to define review criteria + * [ZNTA-2215](https://zanata.atlassian.net/browse/ZNTA-2215) - Reject criteria in gwt editor + * [ZNTA-2197](https://zanata.atlassian.net/browse/ZNTA-2197) - Unmaintained Hibernate Search config for some entities + * [ZNTA-2176](https://zanata.atlassian.net/browse/ZNTA-2176) - Update project "invite only" to "private" + * [ZNTA-2174](https://zanata.atlassian.net/browse/ZNTA-2174) - Limit push/pull/download resources in private project + * [ZNTA-2173](https://zanata.atlassian.net/browse/ZNTA-2173) - Hide 'private' project + * [ZNTA-1952](https://zanata.atlassian.net/browse/ZNTA-1952) - More focused PR template + * [ZNTA-1694](https://zanata.atlassian.net/browse/ZNTA-1694) - Implement People frontend + * [ZNTA-1668](https://zanata.atlassian.net/browse/ZNTA-1668) - Translator should be able to see untranslated glossary entries + * [ZNTA-1227](https://zanata.atlassian.net/browse/ZNTA-1227) - Switch client/common code from hamcrest assert to assertj + +##### Bug Fixes + * [ZNTA-2367](https://zanata.atlassian.net/browse/ZNTA-2367) - Lucene error + * [ZNTA-2363](https://zanata.atlassian.net/browse/ZNTA-2363) - Fix width of project version language list items + * [ZNTA-2362](https://zanata.atlassian.net/browse/ZNTA-2362) - TM creation date is not aligned properly. + * [ZNTA-2361](https://zanata.atlassian.net/browse/ZNTA-2361) - Review Criteria description field is a text area and not limited + * [ZNTA-2360](https://zanata.atlassian.net/browse/ZNTA-2360) - Review Criteria can be created without defined criteria, is unusable + * [ZNTA-2353](https://zanata.atlassian.net/browse/ZNTA-2353) - Cannot add a Yahoo/Fedora account to an existing account + * [ZNTA-2345](https://zanata.atlassian.net/browse/ZNTA-2345) - Request add version to group Send button permanently disabled + * [ZNTA-2344](https://zanata.atlassian.net/browse/ZNTA-2344) - GWT editor text size in target does not change with option + * [ZNTA-2342](https://zanata.atlassian.net/browse/ZNTA-2342) - React editor: filtering by state breaks keyboard navigation + * [ZNTA-2327](https://zanata.atlassian.net/browse/ZNTA-2327) - Yellowish glossary page. + * [ZNTA-2320](https://zanata.atlassian.net/browse/ZNTA-2320) - Frontend watch tasks failing + * [ZNTA-2317](https://zanata.atlassian.net/browse/ZNTA-2317) - [ERROR] "parseQuery() will be replaced with getOptions()" while zanata build. + * [ZNTA-2307](https://zanata.atlassian.net/browse/ZNTA-2307) - Fix translation count in alpha editor + * [ZNTA-2273](https://zanata.atlassian.net/browse/ZNTA-2273) - Zanata behind proxy may return incorrect url in REST response + * [ZNTA-2247](https://zanata.atlassian.net/browse/ZNTA-2247) - Glossary csv upload assumes column positions + * [ZNTA-2241](https://zanata.atlassian.net/browse/ZNTA-2241) - Translation Status Dropdown Button in Alpha Editor not working + * [ZNTA-2240](https://zanata.atlassian.net/browse/ZNTA-2240) - Alpha editor sidebar closes when any area is clicked + * [ZNTA-2214](https://zanata.atlassian.net/browse/ZNTA-2214) - Fix help redirects + * [ZNTA-2212](https://zanata.atlassian.net/browse/ZNTA-2212) - Consistency of headings, buttons and text in frontend + * [ZNTA-1375](https://zanata.atlassian.net/browse/ZNTA-1375) - Zanata allows invalid email address, fails to send verification email + * [ZNTA-1117](https://zanata.atlassian.net/browse/ZNTA-1117) - Zanata client help needs to be specific about command argument order + * [ZNTA-747](https://zanata.atlassian.net/browse/ZNTA-747) - RTL Support + * [ZNTA-166](https://zanata.atlassian.net/browse/ZNTA-166) - Client reports 405 error when pushing files with no basename + * [ZNTA-110](https://zanata.atlassian.net/browse/ZNTA-110) - Suggestion view should highlight matching text of results when diff view is turned off. + * [ZNTA-68](https://zanata.atlassian.net/browse/ZNTA-68) - Highlight words in text flows that match filter text. + * [ZNTA-53](https://zanata.atlassian.net/browse/ZNTA-53) - Add text flow history to activity panel. + +----------------------- + ## 4.3.3 ##### Bug Fixes * [ZNTA-2303](https://zanata.atlassian.net/browse/ZNTA-2303) - Single open id provider sign in shows 404 after sign in diff --git a/docs/user-guide/glossary/upload-glossaries.md b/docs/user-guide/glossary/upload-glossaries.md index 28278f5560..f918e3fb26 100644 --- a/docs/user-guide/glossary/upload-glossaries.md +++ b/docs/user-guide/glossary/upload-glossaries.md @@ -10,8 +10,8 @@ See [glossary roles and permission](/user-guide/glossary/glossary-roles-permissi ##### header row 1. locale columns: The locale header column must contain a valid locale code. At least one locale code is required for the glossary. First locale column will be used as source. -1. part-of-speech column (optional): The part-of-speech header column, when included, should contain only one value: pos. -1. description column (optional): The description header column, when included, should contain only one value: description. +1. part-of-speech column (optional): The part-of-speech header column, when included, should be only one of: `pos`, `partofspeech` or `part of speech`. +1. description column (optional): The description header column, when included, should be only one of: `desc`, `description` or `definition` . ##### data rows @@ -27,7 +27,7 @@ See [glossary roles and permission](/user-guide/glossary/glossary-roles-permissi ### Upload via Web UI 1. Login into Zanata -1. To upload to the **system glossary**, click `Glossary` menu. +1. To upload to the **system glossary**, click `Glossary` menu. 1. To upload to a **project glossary**, navigate to the project page, click on "Glossary" in the project page. 1. Click on `Import Glossary` on top right corner of the page.
diff --git a/parent/pom.xml b/parent/pom.xml index 96c015cc63..b2583cd3ed 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.zanata parent - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT Zanata Parent POM Parent pom for Zanata modules http://zanata.org/ diff --git a/pom.xml b/pom.xml index 9d9952c6fe..1dc4b4ee1f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.zanata parent - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT parent diff --git a/server/functional-test/pom.xml b/server/functional-test/pom.xml index 7c1d20f423..e3df849568 100644 --- a/server/functional-test/pom.xml +++ b/server/functional-test/pom.xml @@ -4,7 +4,7 @@ org.zanata server - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT functional-test diff --git a/server/functional-test/src/main/java/org/zanata/page/AbstractPage.kt b/server/functional-test/src/main/java/org/zanata/page/AbstractPage.kt index 6037a27bd6..e46b299e48 100644 --- a/server/functional-test/src/main/java/org/zanata/page/AbstractPage.kt +++ b/server/functional-test/src/main/java/org/zanata/page/AbstractPage.kt @@ -396,24 +396,55 @@ abstract class AbstractPage(val driver: WebDriver) { } /** - * Retrieve the text from an element + * Enter text into an element, using a locator + * @param findBy locator of element + * @param text text to enter + */ + fun enterText(findBy: By, text: String) { + enterText(readyElement(findBy), text); + } + + /** + * Retrieve the text from an element, using a locator * * @param findBy locator of target element */ fun getText(findBy: By): String { - return readyElement(findBy).getText(); + return getText(existingElement(findBy)); } /** - * Retrieve an attribute value from an element + * Retrieve the text from an element + * + * @param webElement target element + */ + fun getText(webElement: WebElement): String { + scrollIntoView(webElement); + waitForElementReady(webElement); + return webElement.getText(); + } + + /** + * Retrieve an attribute value from an element, by locator * * @param findBy locator of target element * @param attribute name of attribute to query */ fun getAttribute(findBy: By, attribute: String): String { - return existingElement(findBy).getAttribute(attribute); + return getAttribute(existingElement(findBy), attribute) + } + + /** + * Retrieve an attribute value from an element + * + * @param webElement target element + * @param attribute name of attribute to query + */ + fun getAttribute(webElement: WebElement, attribute: String): String { + return webElement.getAttribute(attribute) ?: "" } + /** * 'Touch' a text field to see if it's writable. For cases where fields are * available but briefly won't accept text for some reason diff --git a/server/functional-test/src/main/java/org/zanata/page/CorePage.java b/server/functional-test/src/main/java/org/zanata/page/CorePage.java index be3fc78294..94de357b8a 100644 --- a/server/functional-test/src/main/java/org/zanata/page/CorePage.java +++ b/server/functional-test/src/main/java/org/zanata/page/CorePage.java @@ -45,6 +45,11 @@ public class CorePage extends AbstractPage { private By homeLink = By.id("nav_home"); private By moreLink = By.id("nav_more"); + protected static final By h3Header = By.tagName("h3"); + protected static final By paragraph = By.tagName("p"); + protected static final By inputElement = By.tagName("input"); + protected static final By tableElement = By.tagName("table"); + public CorePage(WebDriver driver) { super(driver); assertNoCriticalErrors(); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/AddLanguagePage.java b/server/functional-test/src/main/java/org/zanata/page/administration/AddLanguagePage.java index 06635fbdab..452a577380 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/AddLanguagePage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/AddLanguagePage.java @@ -31,6 +31,7 @@ public class AddLanguagePage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AddLanguagePage.class); + private By saveButton = By.id("btn-new-language-save"); private By localeId = By.className("react-autosuggest__input"); private By suggestList = @@ -38,19 +39,32 @@ public class AddLanguagePage extends BasePage { private By suggestRow = By.className("react-autosuggest__suggestion"); private By enabledByDefaultCheckbox = By.id("chk-new-language-enabled"); private By pluralsWarning = By.id("new-language-pluralforms-warning"); + private By languageOption = By.name("new-language-displayName"); + private By newLanguageName = By.id("new-language-name"); + private By newLanguageNativeName = By.id("new-language-nativeName"); public AddLanguagePage(final WebDriver driver) { super(driver); } + /** + * Enter a string into the language search field + * @param language string to enter + * @return new AddLanguagePage + */ public AddLanguagePage enterSearchLanguage(String language) { log.info("Enter language {}", language); - enterText(readyElement(localeId), language); + enterText(localeId, language); // Pause for a moment, as quick actions can break here slightPause(); return new AddLanguagePage(getDriver()); } + /** + * Select a language from the search dropdown + * @param language option to select + * @return new AddLanguagePage + */ public AddLanguagePage selectSearchLanguage(final String language) { log.info("Select language {}", language); waitForAMoment().withMessage("language to be clicked") @@ -59,8 +73,8 @@ public AddLanguagePage selectSearchLanguage(final String language) { existingElement(suggestList).findElements(suggestRow); boolean clickedLanguage = false; for (WebElement row : suggestions) { - if (row.findElement(By.name("new-language-displayName")) - .getText().contains(language)) { + if (existingElement(row, languageOption).getText() + .contains(language)) { row.click(); clickedLanguage = true; break; @@ -71,6 +85,10 @@ public AddLanguagePage selectSearchLanguage(final String language) { return new AddLanguagePage(getDriver()); } + /** + * Wait for the plurals warning to show + * @return new AddLanguagePage + */ public AddLanguagePage expectPluralsWarning() { log.info("Expect plurals warning"); waitForPageSilence(); @@ -78,6 +96,10 @@ public AddLanguagePage expectPluralsWarning() { return new AddLanguagePage(getDriver()); } + /** + * Click Enable by default if not already enabled + * @return new AddLanguagePage + */ public AddLanguagePage enableLanguageByDefault() { log.info("Click Enable by default"); if (!readyElement(enabledByDefaultCheckbox).isSelected()) { @@ -86,6 +108,10 @@ public AddLanguagePage enableLanguageByDefault() { return new AddLanguagePage(getDriver()); } + /** + * Click Disable by default if not already disabled + * @return new AddLanguagePage + */ public AddLanguagePage disableLanguageByDefault() { log.info("Click Disable by default"); if (readyElement(enabledByDefaultCheckbox).isSelected()) { @@ -94,20 +120,34 @@ public AddLanguagePage disableLanguageByDefault() { return new AddLanguagePage(getDriver()); } + /** + * Retrieve the name for the new language + * @return String language name + */ public String getNewLanguageName() { - return existingElement(By.id("new-language-name")) - .getAttribute("value"); + return getAttribute(newLanguageName, "value"); } + /** + * Retrieve the native name for the new language + * @return String native language name + */ public String getNewLanguageNativeName() { - return existingElement(By.id("new-language-nativeName")) - .getAttribute("value"); + return getAttribute(newLanguageNativeName, "value"); } + /** + * Retrieve the locale code for the new language + * @return String language locale code + */ public String getNewLanguageCode() { - return existingElement(localeId).getAttribute("value"); + return getAttribute(localeId, "value"); } + /** + * Press the Save button + * @return new LanguagesPage + */ public LanguagesPage saveLanguage() { log.info("Click Save"); clickAndCheckErrors(readyElement(saveButton)); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/AdministrationPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/AdministrationPage.java index 85d02409ea..898f7e7f29 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/AdministrationPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/AdministrationPage.java @@ -27,6 +27,7 @@ public class AdministrationPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AdministrationPage.class); + private final By CONFIGURE_SERVER_LINK = By.id("Admin_Server_configuration_home"); private final By MANAGE_USER_LINK = By.id("Admin_Manage_users_home"); @@ -39,29 +40,50 @@ public AdministrationPage(WebDriver driver) { super(driver); } + /** + * Click the Server Configuration button + * @return new ServerConfigurationPage + */ public ServerConfigurationPage goToServerConfigPage() { log.info("Click Server Configuration"); clickLinkAfterAnimation(CONFIGURE_SERVER_LINK); return new ServerConfigurationPage(getDriver()); } + /** + * Click the Manage Users button + * @return new ManageUserPage + */ public ManageUserPage goToManageUserPage() { log.info("Click Manage Users"); clickLinkAfterAnimation(MANAGE_USER_LINK); return new ManageUserPage(getDriver()); } + /** + * Click the Translation Memory button + * @return new TranslationMemoryPage + */ public TranslationMemoryPage goToTranslationMemoryPage() { log.info("Click Translation Memory"); clickLinkAfterAnimation(MANAGE_TM_LINK); return new TranslationMemoryPage(getDriver()); } + /** + * Click the Manage Search button + * @return new ManageSearchPage + */ public ManageSearchPage goToManageSeachPage() { + log.info("Click Manage Search"); clickLinkAfterAnimation(MANAGE_SEARCH_LINK); return new ManageSearchPage(getDriver()); } + /** + * Click the Manage Roles button + * @return new RoleAssignmentsPage + */ public RoleAssignmentsPage goToManageRoleAssignments() { log.info("Click Manage Roles"); clickLinkAfterAnimation(MANAGE_ROLES_ASSIGN_LINK); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/CreateUserAccountPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/CreateUserAccountPage.java index d88266987c..c036091b8a 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/CreateUserAccountPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/CreateUserAccountPage.java @@ -34,6 +34,7 @@ public class CreateUserAccountPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CreateUserAccountPage.class); + private By usernameField = By.id("newUserForm:username:input:username"); private By emailField = By.id("newUserForm:email:input:email"); private By saveButton = By.id("newUserForm:newUserSave"); @@ -52,40 +53,73 @@ public CreateUserAccountPage(WebDriver driver) { roleMap.put("user", "4"); } + /** + * Enter text into the username field + * @param username string to enter + * @return new CreateUserAccountPage + */ public CreateUserAccountPage enterUsername(String username) { - enterText(readyElement(usernameField), username); + log.info("Enter username {}", username); + enterText(usernameField, username); return new CreateUserAccountPage(getDriver()); } + /** + * Enter text into the email address field + * @param email string to enter + * @return new CreateUserAccountPage + */ public CreateUserAccountPage enterEmail(String email) { - enterText(readyElement(emailField), email); + enterText(emailField, email); return new CreateUserAccountPage(getDriver()); } + /** + * Select a role for the user + * @param role entry to select + * @return new CreateUserAccountPage + */ public CreateUserAccountPage clickRole(String role) { log.info("Click role {}", role); - clickElement(readyElement(By.id(roleIdPrefix.concat(roleMap.get(role))))); + clickElement(By.id(roleIdPrefix.concat(roleMap.get(role)))); return new CreateUserAccountPage(getDriver()); } + /** + * Query if a role is checked + * @param role entry to query + * @return new CreateUserAccountPage + */ public boolean isRoleChecked(String role) { log.info("Query is role {} checked", role); return readyElement(By.id(roleIdPrefix.concat(roleMap.get(role)))) .isSelected(); } + /** + * Press the Save button + * @return new ManageUserPage + */ public ManageUserPage saveUser() { log.info("Click Save"); clickElement(saveButton); return new ManageUserPage(getDriver()); } + /** + * Press the Save button, expecting a failure condition + * @return new CreateUserAccountPage + */ public CreateUserAccountPage saveUserExpectFailure() { - log.info("Click Save"); + log.info("Click Save, expecting failure"); clickElement(saveButton); return new CreateUserAccountPage(getDriver()); } + /** + * Press the Cancel button + * @return new ManageUserPage + */ public ManageUserPage cancelEditUser() { log.info("Click Cancel"); clickElement(cancelButton); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/EditHomeContentPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/EditHomeContentPage.java index 605a252c51..72e9738ac7 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/EditHomeContentPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/EditHomeContentPage.java @@ -32,25 +32,40 @@ public class EditHomeContentPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EditHomeContentPage.class); + private By updateButton = By.id("homeContentForm:update"); private By cancelButton = By.id("homeContentForm:cancel"); + private By homeContent = By.id("homeContentForm:homeContent"); public EditHomeContentPage(final WebDriver driver) { super(driver); } + /** + * Enter text into the content editing panel + * @param text string to enter + * @return new EditHomeContentPage + */ public EditHomeContentPage enterText(String text) { log.info("Enter homepage code\n{}", text); - readyElement(By.id("homeContentForm:homeContent")).sendKeys(text); + enterText(homeContent, text); return new EditHomeContentPage(getDriver()); } + /** + * Press the Update button + * @return new HomePage + */ public HomePage update() { log.info("Click Update"); clickElement(updateButton); return new HomePage(getDriver()); } + /** + * Press the Cancel button + * @return new HomePage + */ public HomePage cancelUpdate() { log.info("Click Cancel"); clickElement(cancelButton); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java index 8dffe2d85a..1d24af41e6 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java @@ -32,6 +32,7 @@ public class EditRoleAssignmentPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EditRoleAssignmentPage.class); + private By policySelect = By.id("role-rule-form:policyName:input:policyName"); private By patternField = @@ -44,31 +45,54 @@ public EditRoleAssignmentPage(WebDriver driver) { super(driver); } + /** + * Select a role assignment policy + * @param policy string to select + * @return new EditRoleAssignmentPage + */ public EditRoleAssignmentPage selectPolicy(String policy) { log.info("Select policy {}", policy); new Select(readyElement(policySelect)).selectByValue(policy); return new EditRoleAssignmentPage(getDriver()); } + /** + * Enter an identity pattern for matching a username + * @param pattern string to enter + * @return new EditRoleAssignmentPage + */ public EditRoleAssignmentPage enterIdentityPattern(String pattern) { log.info("Enter identity pattern {}", pattern); readyElement(patternField).clear(); - enterText(readyElement(patternField), pattern); + enterText(patternField, pattern); return new EditRoleAssignmentPage(getDriver()); } + /** + * Select a role to assign + * @param role string to select + * @return new EditRoleAssignmentPage + */ public EditRoleAssignmentPage selectRole(String role) { log.info("Select role {}", role); new Select(readyElement(roleSelect)).selectByValue(role); return new EditRoleAssignmentPage(getDriver()); } + /** + * Press the Save button + * @return new RoleAssignmentsPage + */ public RoleAssignmentsPage saveRoleAssignment() { log.info("Click Save"); clickElement(saveButton); return new RoleAssignmentsPage(getDriver()); } + /** + * Press the Cancel button + * @return new RoleAssignmentsPage + */ public RoleAssignmentsPage cancelEditRoleAssignment() { log.info("Click Cancel"); clickElement(cancelButton); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/ManageSearchPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/ManageSearchPage.java index ec4dd0b70b..8ddb1e73be 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/ManageSearchPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/ManageSearchPage.java @@ -20,8 +20,10 @@ */ package org.zanata.page.administration; +import java.util.Date; import java.util.List; +import org.joda.time.DateTime; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -40,8 +42,8 @@ public class ManageSearchPage extends BasePage { private static final int SELECT_ALL_COLUMN = 0; private By classesTable = By.id("form:actions"); private By abortButton = By.id("form:cancel"); - private By selectAllButton = By.id("form:selectAll"); - private By performButton = By.id("form:reindex"); + private By selectAllButton = By.id("form:selectAllCheck"); + private By performButton = By.id("reindex"); private By cancelButton = By.linkText("Abort"); private By noOpsLabel = By.id("noOperationsRunning"); private By abortedLabel = By.id("aborted"); @@ -51,6 +53,11 @@ public ManageSearchPage(WebDriver driver) { super(driver); } + /** + * Select all of the available actions for a data type + * @param clazz data type to select + * @return new ManageSearchPage + */ public ManageSearchPage selectAllActionsFor(String clazz) { List tableRows = WebElementUtil.getTableRows(getDriver(), readyElement(classesTable)); @@ -58,13 +65,17 @@ public ManageSearchPage selectAllActionsFor(String clazz) { if (tableRow.getCellContents().contains(clazz)) { WebElement allActionsChkBox = tableRow.getCells().get(SELECT_ALL_COLUMN) - .findElement(By.tagName("input")); + .findElement(inputElement); Checkbox.of(allActionsChkBox).check(); } } return new ManageSearchPage(getDriver()); } + /** + * Press the Select All button + * @return new ManageSearchPage + */ public ManageSearchPage clickSelectAll() { log.info("Click Select All"); clickElement(selectAllButton); @@ -78,24 +89,37 @@ public ManageSearchPage clickSelectAll() { return new ManageSearchPage(getDriver()); } + /** + * Query if all actions in the table are selected + * @return boolean all actions are selected + */ public boolean allActionsSelected() { log.info("Query all actions selected"); List tableRows = WebElementUtil.getTableRows(getDriver(), readyElement( - existingElement(classesTable), By.tagName("table"))); + existingElement(classesTable), tableElement)); for (TableRow tableRow : tableRows) { // column 2, 3, 4 are checkboxes for purge, reindex and optimize for (int i = 1; i <= 3; i++) { - WebElement checkBox = tableRow.getHeaders().get(i) - .findElement(By.tagName("input")); - if (!Checkbox.of(checkBox).checked()) { - return false; + try { + WebElement checkBox = tableRow.getCells().get(i) + .findElement(inputElement); + if (!Checkbox.of(checkBox).checked()) { + return false; + } + } catch (IndexOutOfBoundsException e) { + throw new RuntimeException("Test oops: " + + tableRow.toString() + ":" + i); } } } return true; } + /** + * Press the Perform All Actions button and wait for 'Abort' to be displayed + * @return new ManageSearchPage + */ public ManageSearchPage performSelectedActions() { log.info("Click Perform Actions"); clickElement(performButton); @@ -104,6 +128,10 @@ public ManageSearchPage performSelectedActions() { return new ManageSearchPage(getDriver()); } + /** + * Wait for the Perform All Actions button to become available + * @return new ManageSearchPage + */ public ManageSearchPage expectActionsToFinish() { log.info("Wait: all actions are finished"); // once the button re-appears, it means the reindex is done. @@ -111,22 +139,38 @@ public ManageSearchPage expectActionsToFinish() { return new ManageSearchPage(getDriver()); } + /** + * Press the Abort button + * @return new ManageSearchPage + */ public ManageSearchPage abort() { log.info("Click Abort"); clickElement(abortButton); return new ManageSearchPage(getDriver()); } + /** + * Check if the 'No Operations Running' label is displayed + * @return boolean of label displayed + */ public boolean noOperationsRunningIsDisplayed() { log.info("Query No Operations"); return readyElement(noOpsLabel).isDisplayed(); } + /** + * Check if the 'All Operations Completed' label is displayed + * @return boolean of label displayed + */ public boolean completedIsDisplayed() { log.info("Query is action completed"); return readyElement(completedLabel).isDisplayed(); } + /** + * Check if the 'Operations Aborted' label is displayed + * @return boolean of label displayed + */ public boolean abortedIsDisplayed() { log.info("Query is action aborted"); return readyElement(abortedLabel).isDisplayed(); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java index e30671c64d..49043c4256 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java @@ -33,6 +33,7 @@ public class ManageUserAccountPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ManageUserAccountPage.class); + public static String PASSWORD_ERROR = "Passwords do not match"; private By passwordField = By.id("userdetailForm:password:input:password"); private By passwordConfirmField = @@ -42,6 +43,7 @@ public class ManageUserAccountPage extends BasePage { private By saveButton = By.id("userdetailForm:userdetailSave"); private By cancelButton = By.id("userdetailForm:userdetailCancel"); private Map roleMap; + private String rolePrefix = "userdetailForm:roles:input:roles:"; public ManageUserAccountPage(WebDriver driver) { super(driver); @@ -53,61 +55,104 @@ public ManageUserAccountPage(WebDriver driver) { roleMap.put("user", "4"); } + /** + * Enter a display name for the user + * @param fullName string to enter + * @return new ManageUserAccountPage + */ public ManageUserAccountPage enterFullName(String fullName) { log.info("Enter name {}", fullName); - enterText(readyElement(fullNameField), fullName); + enterText(fullNameField, fullName); return new ManageUserAccountPage(getDriver()); } + /** + * Get the current display name for the user + * @return display name string + */ public String getCurrentName() { log.info("Query user's name"); - return readyElement(fullNameField).getAttribute("value"); + return getAttribute(fullNameField, "value"); } + /** + * Enter a password for the user + * @param password string to enter + * @return new ManageUserAccountPage + */ public ManageUserAccountPage enterPassword(String password) { log.info("Enter password {}", password); - enterText(readyElement(passwordField), password); + enterText(passwordField, password); return new ManageUserAccountPage(getDriver()); } + /** + * Enter a confirmation password for the user + * @param confirmPassword string to enter + * @return new ManageUserAccountPage + */ public ManageUserAccountPage enterConfirmPassword(String confirmPassword) { log.info("Enter confirm password {}", confirmPassword); - enterText(readyElement(passwordConfirmField), confirmPassword); + enterText(passwordConfirmField, confirmPassword); return new ManageUserAccountPage(getDriver()); } + /** + * Press the enabled checkbox + * @return new ManageUserAccountPage + */ public ManageUserAccountPage clickEnabled() { log.info("Click Enabled"); clickElement(enabledField); return new ManageUserAccountPage(getDriver()); } + /** + * Press a named role checkbox + * @param role checkbox name to select + * @return new ManageUserAccountPage + */ public ManageUserAccountPage clickRole(String role) { log.info("Click role {}", role); - clickElement(readyElement(By.id("userdetailForm:roles:input:roles:" - .concat(roleMap.get(role))))); + clickElement(By.id(rolePrefix.concat(roleMap.get(role)))); return new ManageUserAccountPage(getDriver()); } + /** + * Query if a named role is checked + * @param role name to query + * @return boolean is role checked + */ public boolean isRoleChecked(String role) { log.info("Query is role {} checked", role); - return readyElement(By.id( - "userdetailForm:rolesField:roles:".concat(roleMap.get(role)))) + return readyElement(By.id(rolePrefix.concat(roleMap.get(role)))) .isSelected(); } + /** + * Press the Save button + * @return new ManageUserPage + */ public ManageUserPage saveUser() { log.info("Click Save"); clickElement(saveButton); return new ManageUserPage(getDriver()); } + /** + * Press the Save button, expecting a failure condition + * @return new ManageUserAccountPage + */ public ManageUserAccountPage saveUserExpectFailure() { - log.info("Click Save"); + log.info("Click Save, expecting failure"); clickElement(saveButton); return new ManageUserAccountPage(getDriver()); } + /** + * Press the Cancel button + * @return new ManageUserPage + */ public ManageUserPage cancelEditUser() { log.info("Click Cancel"); clickElement(cancelButton); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/ManageUserPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/ManageUserPage.java index f39895ed69..4620ba438a 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/ManageUserPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/ManageUserPage.java @@ -35,18 +35,65 @@ public class ManageUserPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ManageUserPage.class); + private By userTable = By.id("usermanagerForm"); + private By lockIcon = By.className("i--lock"); + private By listEntry = By.className("list__item--actionable"); + private By actionsDropdown = By.id("rolemanage-more-actions"); + private By createUser = By.linkText("Create new user"); public ManageUserPage(WebDriver driver) { super(driver); } + /** + * Press the edit button on a user account + * @param username of account to edit + * @return new ManageUserAccountPage + */ public ManageUserAccountPage editUserAccount(String username) { log.info("Click edit on {}", username); clickElement(findRowByUserName(username)); return new ManageUserAccountPage(getDriver()); } + /** + * Get a list of user names + * @return String list of user names + */ + public List getUserList() { + log.info("Query user list"); + // Page may refresh user list + waitForPageSilence(); + List names = new ArrayList<>(); + for (WebElement element : getRows()) { + names.add(getListItemUsername(element)); + } + return names; + } + + /** + * Open the menu and select Create New User + * @return new CreateUserAccountPage + */ + public CreateUserAccountPage selectCreateNewUser() { + log.info("Click Create new user"); + clickElement(actionsDropdown); + clickLinkAfterAnimation(createUser); + return new CreateUserAccountPage(getDriver()); + } + + /** + * Query if user is enabled + * @param username to query + * @return boolean user is enabled + */ + public boolean isUserEnabled(String username) { + log.info("Query is user {} enabled", username); + return findRowByUserName(username).findElements(lockIcon).isEmpty(); + } + + // Find the row WebElement containing the username private WebElement findRowByUserName(final String username) { for (WebElement listItem : getRows()) { if (getListItemUsername(listItem).equals(username)) { @@ -63,49 +110,27 @@ private WebElement findRowByUserName(final String username) { return listItem; } } - return null; - } - - public boolean isUserEnabled(String username) { - log.info("Query is user {} enabled", username); - List locks = findRowByUserName(username) - .findElements(By.className("i--lock")); - return locks.isEmpty(); + throw new RuntimeException("Search for username " + username + + " failed"); } - public List getRows() { - return readyElement(userTable) - .findElements(By.className("list__item--actionable")); + // Retrieve all of the user rows from the table + private List getRows() { + return readyElement(userTable).findElements(listEntry); } - public String getListItemUsername(WebElement listItem) { + // Retrieve the username from a user row + private String getListItemUsername(WebElement listItem) { String listItemText = listItem.findElement(By.tagName("h3")).getText(); return listItemText - .substring(0, - listItemText.lastIndexOf(getListItemRoles(listItem))) + .substring(0, listItemText.lastIndexOf(getListItemRoles(listItem))) .trim(); } - public String getListItemRoles(WebElement listItem) { + // Retrieve the list of roles, as a string, from a user row + private String getListItemRoles(WebElement listItem) { return listItem.findElement(By.tagName("h3")) .findElement(By.className("txt--meta")).getText(); } - public List getUserList() { - log.info("Query user list"); - // Page may refresh user list - waitForPageSilence(); - List names = new ArrayList<>(); - for (WebElement element : getRows()) { - names.add(getListItemUsername(element)); - } - return names; - } - - public CreateUserAccountPage selectCreateNewUser() { - log.info("Click Create new user"); - clickElement(By.id("rolemanage-more-actions")); - clickLinkAfterAnimation(By.linkText("Create new user")); - return new CreateUserAccountPage(getDriver()); - } } diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/RoleAssignmentsPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/RoleAssignmentsPage.java index 2fae39b515..78c2a4d73c 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/RoleAssignmentsPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/RoleAssignmentsPage.java @@ -34,6 +34,7 @@ public class RoleAssignmentsPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RoleAssignmentsPage.class); + private By moreActions = By.id("roleassign-more-actions"); private By newRuleButton = By.linkText("New Rule"); private By roleTable = By.className("list--stats"); @@ -42,30 +43,34 @@ public RoleAssignmentsPage(WebDriver driver) { super(driver); } + /** + * Press the More Actions dropdown + * @return new RoleAssignmentsPage + */ public RoleAssignmentsPage clickMoreActions() { log.info("Click More Actions dropdown"); clickElement(moreActions); return new RoleAssignmentsPage(getDriver()); } - public EditRoleAssignmentPage selectCreateNewRule() { - log.info("Click Create New in dropdown"); - clickLinkAfterAnimation(By.linkText("New Rule")); - return new EditRoleAssignmentPage(getDriver()); - } - + /** + * Select the Create New Rule option from the drop down + * @return new EditRoleAssignmentPage + */ public EditRoleAssignmentPage clickCreateNew() { - log.info("Click Create New"); - clickElement(newRuleButton); + log.info("Click Create New dropdown option"); + clickLinkAfterAnimation(newRuleButton); return new EditRoleAssignmentPage(getDriver()); } + /** + * Query the rules, returning a list of patterns + * @return list of pattern strings + */ public List getRulesByPattern() { log.info("Query role rules"); List ret = new ArrayList<>(); - List names = - WebElementUtil.elementsToText(getDriver(), roleTable); - for (String name : names) { + for (String name : WebElementUtil.elementsToText(getDriver(), roleTable)) { ret.add(name.substring(name.lastIndexOf(':') + 1).trim()); } return ret; diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java index 3b5cef904d..1722364a22 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java @@ -33,6 +33,7 @@ public class ServerConfigurationPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServerConfigurationPage.class); + private By urlField = By.id("serverConfigForm:url:input:url"); public static final By registerUrlField = By.id("serverConfigForm:register:input:registerUrl"); @@ -59,130 +60,229 @@ public ServerConfigurationPage(WebDriver driver) { super(driver); } + // Ensure text is entered into the config fields private void enterTextConfigField(By by, String text) { touchTextField(readyElement(by)); enterText(readyElement(by), text); expectFieldValue(by, text); } + /** + * Wait until the field contains the expected text + * @param by locator of field + * @param expectedValue text to compare actual value to + * @return new ServerConfigurationPage + */ + public boolean expectFieldValue(final By by, final String expectedValue) { + log.info("Wait for field {} value {}", by.toString(), expectedValue); + return waitForAMoment().withMessage("text present: " + by.toString()) + .until(ExpectedConditions.textToBePresentInElementValue( + existingElement(by), expectedValue)); + } + + /** + * Enter text into the server url field + * @param url text to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputServerURL(String url) { log.info("Enter Server URL {}", url); enterTextConfigField(urlField, url); return new ServerConfigurationPage(getDriver()); } + /** + * Enter text into the registration url field + * @param url text to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputRegisterURL(String url) { log.info("Enter Register URL {}", url); enterTextConfigField(registerUrlField, url); return new ServerConfigurationPage(getDriver()); } - public boolean expectFieldValue(final By by, final String expectedValue) { - log.info("Wait for field {} value {}", by.toString(), expectedValue); - return waitForAMoment().withMessage("text present: " + by.toString()) - .until(ExpectedConditions.textToBePresentInElementValue( - existingElement(by), expectedValue)); - } - + /** + * Enter text into the admin email field + * @param email text to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputAdminEmail(String email) { log.info("Enter admin email address {}", email); enterTextConfigField(adminEmailField, email); return new ServerConfigurationPage(getDriver()); } + /** + * Enter text into the admin 'from' email field + * @param email text to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputAdminFromEmail(String email) { log.info("Enter admin From email address {}", email); enterTextConfigField(fromEmailField, email); return new ServerConfigurationPage(getDriver()); } + /** + * Enter text into the help url field + * @param url text to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputHelpURL(String url) { log.info("Enter Help URL {}", url); enterTextConfigField(helpUrlField, url); return new ServerConfigurationPage(getDriver()); } + /** + * Enter text into the terms of use url field + * @param url text to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputTermsOfUseURL(String url) { log.info("Enter Terms of Use URL {}", url); enterTextConfigField(termsUrlField, url); return new ServerConfigurationPage(getDriver()); } + /** + * Click to select or deselect the logging enabled checkbox + * @return new ServerConfigurationPage + */ public ServerConfigurationPage clickLoggingEnabledCheckbox() { log.info("Click enable logging checkbox"); clickElement(enableLogCheck); return new ServerConfigurationPage(getDriver()); } + /** + * Select a given log level + * @param logLevel log level value to select + * @return new ServerConfigurationPage + */ public ServerConfigurationPage selectLoggingLevel(String logLevel) { log.info("Select logging level {}", logLevel); new Select(readyElement(logLevelSelect)).selectByVisibleText(logLevel); return new ServerConfigurationPage(getDriver()); } + /** + * Query the currently set log level + * @return log level string + */ public String selectedLoggingLevel() { log.info("Query selected logging level"); return new Select(readyElement(logLevelSelect)).getFirstSelectedOption() .getText(); } + /** + * Enter text into the log email field + * @param email text to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputLogEmailTarget(String email) { log.info("Enter log email target {}", email); enterTextConfigField(emailDestinationField, email); return new ServerConfigurationPage(getDriver()); } + /** + * Query the value of the log email field + * @return new ServerConfigurationPage + */ public String getLogEmailTarget() { log.info("Query log email target"); - return readyElement(emailDestinationField).getAttribute("value"); + return getAttribute(emailDestinationField, "value"); } + /** + * Enter text into the piwik url field + * @param url text to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputPiwikUrl(String url) { log.info("Enter Piwik URL: {}", url); enterTextConfigField(piwikUrl, url); return new ServerConfigurationPage(getDriver()); } + /** + * Query the value of the piwik url field + * @return new ServerConfigurationPage + */ public String getPiwikUrl() { log.info("Query Piwik URL"); - return readyElement(piwikUrl).getAttribute("value"); + return getAttribute(piwikUrl, "value"); } + /** + * Enter text into the piwik ID field + * @param id text to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputPiwikID(String id) { log.info("Enter Piwik ID: {}", id); enterTextConfigField(piwikId, id); return new ServerConfigurationPage(getDriver()); } + /** + * Query the value of the piwik ID field + * @return String of value in piwik ID field + */ public String getPiwikID() { log.info("Query Piwik ID"); - return readyElement(piwikId).getAttribute("value"); + return getAttribute(piwikId, "value"); } + /** + * Enter a value into the maximum concurrent requests field + * @param max number to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputMaxConcurrent(int max) { log.info("Enter maximum concurrent API requests {}", max); readyElement(maxConcurrentField).clear(); - enterText(readyElement(maxConcurrentField), max + ""); + enterText(maxConcurrentField, String.valueOf(max)); return new ServerConfigurationPage(getDriver()); } + /** + * Retrieve the value from the max concurrent requests field + * @return String of value in max concurrent requests field + */ public String getMaxConcurrentRequestsPerApiKey() { log.info("Query maximum concurrent API requests"); - return readyElement(maxConcurrentField).getAttribute("value"); + return getAttribute(maxConcurrentField, "value"); } + /** + * Enter a value into the maximum active requests field + * @param max value to enter + * @return new ServerConfigurationPage + */ public ServerConfigurationPage inputMaxActive(int max) { log.info("Enter maximum active API requests {}", max); readyElement(maxActiveField).clear(); - enterText(readyElement(maxActiveField), max + ""); + enterText(maxActiveField, String.valueOf(max)); return new ServerConfigurationPage(getDriver()); } + /** + * Retrieve the value from the maximum active requests field + * @return String of maximum active requests value + */ public String getMaxActiveRequestsPerApiKey() { log.info("Query maximum active API requests"); - return readyElement(maxActiveField).getAttribute("value"); + return getAttribute(maxActiveField, "value"); } + /** + * Press the Save button + * @return new AdministrationPage + */ public AdministrationPage save() { log.info("Click Save"); clickElement(saveButton); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java index 016750202c..6a4c209bfb 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java @@ -31,6 +31,7 @@ public class TranslationMemoryEditPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TranslationMemoryEditPage.class); + private By idField = By.id("tmForm:slug:input:slug"); private By descriptionField = By.id("tmForm:description:input:description"); private By saveButton = By.id("tmForm:save"); @@ -40,31 +41,52 @@ public TranslationMemoryEditPage(WebDriver driver) { super(driver); } + /** + * Enter an ID for the translation memory + * @param id to enter + * @return new TranslationMemoryEditPage + */ public TranslationMemoryEditPage enterMemoryID(String id) { log.info("Enter TM ID {}", id); - enterText(readyElement(idField), id); + enterText(idField, id); return new TranslationMemoryEditPage(getDriver()); } - public TranslationMemoryEditPage - enterMemoryDescription(String description) { + /** + * Enter a description for the translation memory + * @param description to enter + * @return new TranslationMemoryEditPage + */ + public TranslationMemoryEditPage enterTMDescription(String description) { log.info("Enter TM description {}", description); - enterText(readyElement(descriptionField), description); + enterText(descriptionField, description); return new TranslationMemoryEditPage(getDriver()); } + /** + * Press the Save button + * @return new TranslationMemoryPage + */ public TranslationMemoryPage saveTM() { log.info("Click Save"); clickElement(saveButton); return new TranslationMemoryPage(getDriver()); } + /** + * Press the Save button, expecting a failure condition + * @return new TranslationMemoryEditPage + */ public TranslationMemoryEditPage clickSaveAndExpectFailure() { log.info("Click Save"); clickElement(saveButton); return new TranslationMemoryEditPage(getDriver()); } + /** + * Press the Cancel button + * @return new TranslationMemoryPage + */ public TranslationMemoryPage cancelTM() { log.info("Click Cancel"); clickElement(cancelButton); diff --git a/server/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryPage.java b/server/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryPage.java index 0e5d5fb0c1..694f541822 100644 --- a/server/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryPage.java +++ b/server/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryPage.java @@ -24,6 +24,7 @@ import org.zanata.page.BasePage; import java.util.ArrayList; import java.util.List; +import static org.apache.commons.lang3.StringUtils.isBlank; /** * @author Damian Jansen @@ -32,11 +33,13 @@ public class TranslationMemoryPage extends BasePage { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TranslationMemoryPage.class); - public static final String ID_UNAVAILABLE = "This ExplicitId is not available"; + + public static final String ID_UNAVAILABLE = "This Id is not available"; public static final String UPLOAD_ERROR = "There was an error uploading the file"; public static final String NO_MEMORIES = "No Translation Memories have been created."; + private By listItemCount = By.className("badge"); private By listItemDescription = By.className("list__item__meta"); private By dropDownMenu = By.id("moreActions"); @@ -58,6 +61,10 @@ public TranslationMemoryPage(WebDriver driver) { super(driver); } + /** + * Press the Create New button in the dropdown menu + * @return new TranslationMemoryEditPage + */ public TranslationMemoryEditPage clickCreateNew() { log.info("Click Create New"); clickElement(dropDownMenu); @@ -65,27 +72,47 @@ public TranslationMemoryEditPage clickCreateNew() { return new TranslationMemoryEditPage(getDriver()); } + /** + * Press the dropdown menu for a specific TM entry + * @param tmName of entry to press the menu for + * @return + */ public TranslationMemoryPage clickOptions(String tmName) { log.info("Click Options dropdown for {}", tmName); clickElement(readyElement(findRowByTMName(tmName), listDropDownMenu)); return new TranslationMemoryPage(getDriver()); } + /** + * Press the Import menu option for a specific TM entry + * The dropdown option menu should be opened before this action + * @param tmName of entry to press Import for + * @return new TranslationMemoryPage + */ public TranslationMemoryPage clickImport(String tmName) { log.info("Click Import"); clickElement(readyElement(findRowByTMName(tmName), listImportButton)); return new TranslationMemoryPage(getDriver()); } + /** + * Enter a filename of a TM to import directly into the import dialog + * @param importFileName of file to import + * @return new TranslationMemoryPage + */ public TranslationMemoryPage enterImportFileName(String importFileName) { log.info("Enter import TM filename {}", importFileName); - // Don't clear, inject text, check value + // Don't clear, inject text, do not check value enterText(readyElement(filenameInput), importFileName, false, true, false); slightPause(); return new TranslationMemoryPage(getDriver()); } + /** + * Press the Upload button on the import dialog + * @return new TranslationMemoryPage + */ public TranslationMemoryPage clickUploadButtonAndAcknowledge() { log.info("Click and accept Upload button"); clickElement(uploadButton); @@ -95,51 +122,77 @@ public TranslationMemoryPage clickUploadButtonAndAcknowledge() { return new TranslationMemoryPage(getDriver()); } + /** + * Determine if the Upload button is enabled + * @return boolean button is enabled + */ public boolean isImportButtonEnabled() { - WebElement element = existingElement(uploadButton); - return element.isEnabled(); + return existingElement(uploadButton).isEnabled(); } + /** + * Press the Clear button, then press the Accept button, for a specific TM + * @param tmName of TM to clear + * @return new TranslationMemoryPage + */ public TranslationMemoryPage clickClearTMAndAccept(String tmName) { log.info("Click and accept Clear {}", tmName); clickElement(readyElement(findRowByTMName(tmName), listClearButton)); - clickElement( - readyElement(clearConfirmation).findElement(okConfirmation)); + clickElement(readyElement(existingElement(clearConfirmation), okConfirmation)); return new TranslationMemoryPage(getDriver()); } + /** + * Press the Clear button, then press the Cancel button, for a specific TM + * @param tmName of TM to press clear and cancel for + * @return new TranslationMemoryPage + */ public TranslationMemoryPage clickClearTMAndCancel(String tmName) { log.info("Click and Cancel Clear {}", tmName); clickElement(readyElement(findRowByTMName(tmName), listClearButton)); - clickElement(readyElement(clearConfirmation) - .findElement(cancelConfirmation)); + clickElement(readyElement(existingElement(clearConfirmation), cancelConfirmation)); return new TranslationMemoryPage(getDriver()); } + /** + * Press the Delete button, then press the Accept button, for a specific TM + * @param tmName of TM to delete + * @return new TranslationMemoryPage + */ public TranslationMemoryPage clickDeleteTmAndAccept(String tmName) { log.info("Click and accept Delete {}", tmName); clickElement(readyElement(findRowByTMName(tmName), listDeleteButton)); slightPause(); - clickElement( - readyElement(deleteConfirmation).findElement(okConfirmation)); + clickElement(readyElement(existingElement(deleteConfirmation), okConfirmation)); return new TranslationMemoryPage(getDriver()); } + /** + * Press the Delete button, then press the Cancel button, for a specific TM + * @param tmName of TM to press delete and cancel for + * @return new TranslationMemoryPage + */ public TranslationMemoryPage clickDeleteTmAndCancel(String tmName) { log.info("Click and cancel Delete {}", tmName); clickElement(readyElement(findRowByTMName(tmName), listDeleteButton)); - clickElement(readyElement(deleteConfirmation) - .findElement(cancelConfirmation)); + clickElement(readyElement(existingElement(deleteConfirmation), cancelConfirmation)); return new TranslationMemoryPage(getDriver()); } + /** + * Dismiss the Import Error dialog + * @return new TranslationMemoryPage + */ public TranslationMemoryPage dismissError() { log.info("Dismiss error dialog"); - clickElement( - readyElement(uploadNotification).findElement(okConfirmation)); + clickElement(readyElement(existingElement(uploadNotification), okConfirmation)); return new TranslationMemoryPage(getDriver()); } + /** + * Retrieve a list of the TM entries + * @return String list of TM names + */ public List getListedTranslationMemorys() { log.info("Query translation memory names"); List names = new ArrayList<>(); @@ -149,50 +202,71 @@ public List getListedTranslationMemorys() { return names; } + /** + * Query a specific TM description + * @param tmName name of TM to query + * @return description String + */ public String getDescription(String tmName) { log.info("Query description {}", tmName); - return getListEntryDescription(findRowByTMName(tmName)); + return getText(existingElement(findRowByTMName(tmName), listItemDescription)); } + /** + * Query a specific TM's number of entries + * @param tmName name of TM to query + * @return number of TM entries as String + */ public String getNumberOfEntries(String tmName) { log.info("Query number of entries {}", tmName); waitForPageSilence(); return getListEntryCount(findRowByTMName(tmName)); } + // TODO Remove this when stable + public void expectNumberOfEntries(int number, String tmName) { + waitForAMoment().withMessage("Workaround: wait for number of entries") + .until(it -> Integer.valueOf(getNumberOfEntries(tmName)) + .equals(number)); + } + + /** + * Query a TM for the delete button being enabled + * @param tmName name of TM to query + * @return boolean delete button is available + */ public boolean canDelete(String tmName) { log.info("Query can delete {}", tmName); - String disabled = - readyElement(findRowByTMName(tmName), listDeleteButton) - .getAttribute("disabled"); - return null == disabled || disabled.equals("false"); + String disabled = getAttribute(existingElement(findRowByTMName(tmName), + listDeleteButton), "disabled"); + return isBlank(disabled) || disabled.equals("false"); } + /* * Check to see if the TM list is empty */ - private boolean noTmsCreated() { - for (WebElement element : readyElement(tmList) - .findElements(By.tagName("p"))) { - if (element.getText().equals(NO_MEMORIES)) { + for (WebElement element : readyElement(tmList).findElements(paragraph)) { + if (getText(element).equals(NO_MEMORIES)) { return true; } } return false; } + /* * Get a row from the TM table that corresponds with tmName */ - private WebElement findRowByTMName(final String tmName) { for (WebElement listElement : getTMList()) { if (getListEntryName(listElement).equals(tmName)) { return listElement; } } - return null; + throw new RuntimeException("Unable to find TM row " + tmName); } + // Get a web element list of all TM entries private List getTMList() { if (noTmsCreated()) { log.info("TM list is empty"); @@ -202,20 +276,17 @@ private List getTMList() { .findElements(By.className("list__item--actionable")); } + // Get the name substring of a TM entry private String getListEntryName(WebElement listElement) { - String title = - listElement.findElement(By.tagName("h3")).getText().trim(); + String title = getText(existingElement(listElement, h3Header)).trim(); return title .substring(0, title.lastIndexOf(getListEntryCount(listElement))) .trim(); } - private String getListEntryDescription(WebElement listElement) { - return readyElement(listElement, listItemDescription).getText(); - } - + // Get the entry count substring for a TM entry private String getListEntryCount(WebElement listElement) { - return listElement.findElement(By.tagName("h3")) - .findElement(listItemCount).getText(); + return getText(existingElement( + existingElement(listElement, h3Header), listItemCount)); } } diff --git a/server/functional-test/src/main/java/org/zanata/workflow/TranslationMemoryWorkFlow.java b/server/functional-test/src/main/java/org/zanata/workflow/TranslationMemoryWorkFlow.java index 9bbec7fcde..a01e3dd39b 100644 --- a/server/functional-test/src/main/java/org/zanata/workflow/TranslationMemoryWorkFlow.java +++ b/server/functional-test/src/main/java/org/zanata/workflow/TranslationMemoryWorkFlow.java @@ -38,6 +38,6 @@ public TranslationMemoryPage createTranslationMemory(String name, String description) { return goToHome().goToAdministration().goToTranslationMemoryPage() .clickCreateNew().enterMemoryID(name) - .enterMemoryDescription(description).saveTM(); + .enterTMDescription(description).saveTM(); } } diff --git a/server/functional-test/src/test/java/org/zanata/feature/administration/AutoRoleAssignmentTest.java b/server/functional-test/src/test/java/org/zanata/feature/administration/AutoRoleAssignmentTest.java index 0bc500ba63..ea93e63ed5 100644 --- a/server/functional-test/src/test/java/org/zanata/feature/administration/AutoRoleAssignmentTest.java +++ b/server/functional-test/src/test/java/org/zanata/feature/administration/AutoRoleAssignmentTest.java @@ -57,7 +57,7 @@ public void createAutoRoleAssignment() throws Exception { roleAssignmentsPage.logout(); { - // TODO: Bug? Remove me + // TODO: ZNTA-440 new LoginWorkFlow() .signIn("translator", "translator") .logout(); diff --git a/server/functional-test/src/test/java/org/zanata/feature/administration/EditHomePageTest.java b/server/functional-test/src/test/java/org/zanata/feature/administration/EditHomePageTest.java index a20cb3ab11..505a3ce4e1 100644 --- a/server/functional-test/src/test/java/org/zanata/feature/administration/EditHomePageTest.java +++ b/server/functional-test/src/test/java/org/zanata/feature/administration/EditHomePageTest.java @@ -57,7 +57,8 @@ public void editPageCode() throws Exception { .enterText("This text contains *some* markup") .update(); - assertThat(homePage.getMainBodyContent()).isEqualTo("This text contains some markup") + assertThat(homePage.getMainBodyContent()) + .isEqualTo("This text contains some markup") .as("Homepage text has been updated"); } } diff --git a/server/functional-test/src/test/java/org/zanata/feature/administration/EditTranslationMemoryTest.java b/server/functional-test/src/test/java/org/zanata/feature/administration/EditTranslationMemoryTest.java index 6ed9cbab7a..a20524f8ed 100644 --- a/server/functional-test/src/test/java/org/zanata/feature/administration/EditTranslationMemoryTest.java +++ b/server/functional-test/src/test/java/org/zanata/feature/administration/EditTranslationMemoryTest.java @@ -96,7 +96,7 @@ public void translationMemoryIdsAreUnique() throws Exception { TranslationMemoryEditPage translationMemoryEditPage = tmMemoryPage .clickCreateNew() .enterMemoryID(nonUniqueTMId) - .enterMemoryDescription("Meh") + .enterTMDescription("Meh") .clickSaveAndExpectFailure(); assertThat(translationMemoryEditPage.getErrors()) @@ -193,8 +193,6 @@ public void dontDeleteTranslationMemory() throws Exception { @Trace(summary = "The administrator can clear the content of a " + "translation memory entry") @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) - // fails intermittently - @Ignore public void clearTranslationMemory() throws Exception { String clearTMId = "cleartmtest"; File importFile = testFileGenerator.openTestFile("test-tmx.xml"); @@ -213,6 +211,10 @@ public void clearTranslationMemory() throws Exception { tmMemoryPage = tmMemoryPage.clickOptions(clearTMId) .clickClearTMAndAccept(clearTMId); + // TODO there seems to be some issue here, fix + tmMemoryPage.reload(); + tmMemoryPage.expectNumberOfEntries(0, clearTMId); + assertThat(tmMemoryPage.getNumberOfEntries(clearTMId)) .isEqualTo("0") .as("The translation memory entries is empty"); @@ -247,8 +249,6 @@ public void dontClearTranslationMemory() throws Exception { @Trace(summary = "The administrator must clear a translation memory " + "entry before it can be deleted") @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) - // fails intermittently - @Ignore public void mustClearBeforeDelete() throws Exception { String forceClear = "forcecleartodelete"; File importFile = testFileGenerator.openTestFile("test-tmx.xml"); @@ -268,7 +268,10 @@ public void mustClearBeforeDelete() throws Exception { .as("The item cannot yet be deleted"); tmMemoryPage = tmMemoryPage.clickClearTMAndAccept(forceClear); - tmMemoryPage.getNumberOfEntries(forceClear); + + // TODO there seems to be some issue here, fix + tmMemoryPage.reload(); + tmMemoryPage.expectNumberOfEntries(0, forceClear); assertThat(tmMemoryPage.clickOptions(forceClear).canDelete(forceClear)) .isTrue() diff --git a/server/functional-test/src/test/java/org/zanata/feature/administration/ManageSearchTest.java b/server/functional-test/src/test/java/org/zanata/feature/administration/ManageSearchTest.java index 5bff7893c6..b0f5dc009e 100644 --- a/server/functional-test/src/test/java/org/zanata/feature/administration/ManageSearchTest.java +++ b/server/functional-test/src/test/java/org/zanata/feature/administration/ManageSearchTest.java @@ -50,7 +50,8 @@ public void before() { @Trace(summary = "The administrator can clear and regenerate all of the " + "search indexes") @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) - @Ignore("RHBZ1180948 JBoss issue") + @Ignore("Unstable - sometimes the button isn't ready and sometimes " + + "the index fails to complete.") public void regenerateSearchIndexes() throws Exception { ManageSearchPage manageSearchPage = dashboardPage .goToAdministration() diff --git a/server/functional-test/src/test/java/org/zanata/feature/administration/ManageUsersTest.java b/server/functional-test/src/test/java/org/zanata/feature/administration/ManageUsersTest.java index 8421403ed1..35c659ac89 100644 --- a/server/functional-test/src/test/java/org/zanata/feature/administration/ManageUsersTest.java +++ b/server/functional-test/src/test/java/org/zanata/feature/administration/ManageUsersTest.java @@ -126,6 +126,7 @@ public void changeUserRoles() throws Exception { .as("The user can access the administration panel"); } + @Trace(summary = "The administrator can change a user account's name") @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) public void changeUsersName() { ManageUserAccountPage manageUserAccountPage = dashboardPage diff --git a/server/functional-test/src/test/java/org/zanata/feature/administration/ServerSettingsTest.java b/server/functional-test/src/test/java/org/zanata/feature/administration/ServerSettingsTest.java index 019a1b0a4f..46ddc824dc 100644 --- a/server/functional-test/src/test/java/org/zanata/feature/administration/ServerSettingsTest.java +++ b/server/functional-test/src/test/java/org/zanata/feature/administration/ServerSettingsTest.java @@ -58,97 +58,135 @@ public void setServerURLTest() { .as("The email indicates the expected server url"); } - @Test + @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) public void setRegisterURLTest() { String url = "http://myserver.com/register"; - ServerConfigurationPage serverConfigurationPage = - new LoginWorkFlow().signIn("admin", "admin") - .goToAdministration().goToServerConfigPage() - .inputRegisterURL(url).save().goToServerConfigPage(); + ServerConfigurationPage serverConfigurationPage = new LoginWorkFlow() + .signIn("admin", "admin") + .goToAdministration() + .goToServerConfigPage() + .inputRegisterURL(url) + .save() + .goToServerConfigPage(); + assertThat(serverConfigurationPage.expectFieldValue( ServerConfigurationPage.registerUrlField, url)) .as("The expected url was displayed"); } - @Test + @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) public void setAdministratorEmailTest() { - new LoginWorkFlow().signIn("admin", "admin").goToAdministration() - .goToServerConfigPage().inputAdminEmail("lara@example.com") - .save().gotoMorePage().clickContactAdmin() - .inputMessage("Test pattern").send(HomePage.class); + new LoginWorkFlow().signIn("admin", "admin") + .goToAdministration() + .goToServerConfigPage() + .inputAdminEmail("lara@example.com") + .save() + .gotoMorePage() + .clickContactAdmin() + .inputMessage("Test pattern") + .send(HomePage.class); + assertThat(hasEmailRule.getMessages().get(0).getEnvelopeReceiver()) .contains("lara@example.com").as("The recipient admin was set"); } - @Test + @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) @Ignore("unstable") public void setAdministratorEmailFromTest() { String email = "lara@example.com"; ServerConfigurationPage serverConfigurationPage = new LoginWorkFlow() - .signIn("admin", "admin").goToAdministration() - .goToServerConfigPage().inputAdminFromEmail(email).save() + .signIn("admin", "admin") + .goToAdministration() + .goToServerConfigPage() + .inputAdminFromEmail(email) + .save() .goToServerConfigPage(); + assertThat(serverConfigurationPage.expectFieldValue( ServerConfigurationPage.fromEmailField, email)); + serverConfigurationPage.goToHomePage().logout(); new RegisterWorkFlow().registerInternal("test1", "test1", "test123", "test1@test.com"); + assertThat(hasEmailRule.getMessages().get(0).getEnvelopeSender()) .contains("lara@example.com") .as("The server email sender was set"); } - @Test + @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) public void setHelpURLTest() { MorePage morePage = new LoginWorkFlow().signIn("admin", "admin") - .goToAdministration().goToServerConfigPage() - .inputHelpURL("http://www.test.com").save().gotoMorePage(); + .goToAdministration() + .goToServerConfigPage() + .inputHelpURL("http://www.test.com") + .save() + .gotoMorePage(); + assertThat(morePage.getHelpURL()).isEqualTo("http://www.test.com/") .as("The help URL was set correctly"); } - @Test + @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) public void unsetTermsOfUseURL() { RegisterPage registerPage = new LoginWorkFlow().signIn("admin", "admin") - .goToAdministration().goToServerConfigPage() - .inputTermsOfUseURL("http://www.test.com").save() - .goToServerConfigPage().inputTermsOfUseURL("").save().logout() + .goToAdministration() + .goToServerConfigPage() + .inputTermsOfUseURL("http://www.test.com") + .save() + .goToServerConfigPage() + .inputTermsOfUseURL("") + .save() + .logout() .goToRegistration(); + assertThat(registerPage.termsOfUseUrlVisible()).isFalse() .as("The Terms of Use URL is not visible"); } - @Test + @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) public void setTermsOfUseURLTest() { RegisterPage registerPage = new LoginWorkFlow().signIn("admin", "admin") - .goToAdministration().goToServerConfigPage() - .inputTermsOfUseURL("http://www.test.com").save().logout() + .goToAdministration() + .goToServerConfigPage() + .inputTermsOfUseURL("http://www.test.com") + .save() + .logout() .goToRegistration(); + assertThat(registerPage.getTermsUrl()).isEqualTo("http://www.test.com/") .as("The Terms of Use URL was set correctly"); } - @Test + @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) public void setEmailLoggingTest() { ServerConfigurationPage serverConfigurationPage = new LoginWorkFlow() - .signIn("admin", "admin").goToAdministration() - .goToServerConfigPage().clickLoggingEnabledCheckbox() + .signIn("admin", "admin") + .goToAdministration() + .goToServerConfigPage() + .clickLoggingEnabledCheckbox() .selectLoggingLevel("Error") - .inputLogEmailTarget("lara@example.com").save() + .inputLogEmailTarget("lara@example.com") + .save() .goToServerConfigPage(); + assertThat(serverConfigurationPage.selectedLoggingLevel()) .isEqualTo("Error").as("Level is correct"); assertThat(serverConfigurationPage.getLogEmailTarget()) .isEqualTo("lara@example.com").as("Recipient is correct"); } - @Test + @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION) public void setPiwikTest() { - ServerConfigurationPage serverConfigurationPage = - new LoginWorkFlow().signIn("admin", "admin") - .goToAdministration().goToServerConfigPage() - .inputPiwikUrl("http://example.com/piwik") - .inputPiwikID("12345").save().goToServerConfigPage(); + ServerConfigurationPage serverConfigurationPage = new LoginWorkFlow() + .signIn("admin", "admin") + .goToAdministration() + .goToServerConfigPage() + .inputPiwikUrl("http://example.com/piwik") + .inputPiwikID("12345") + .save() + .goToServerConfigPage(); + assertThat(serverConfigurationPage.getPiwikUrl()) .isEqualTo("http://example.com/piwik") .as("Piwik url is correct is correct"); diff --git a/server/functional-test/src/test/java/org/zanata/feature/endtoend/AdminEndToEndTest.java b/server/functional-test/src/test/java/org/zanata/feature/endtoend/AdminEndToEndTest.java index 8726db71f1..771d1a39e7 100644 --- a/server/functional-test/src/test/java/org/zanata/feature/endtoend/AdminEndToEndTest.java +++ b/server/functional-test/src/test/java/org/zanata/feature/endtoend/AdminEndToEndTest.java @@ -107,6 +107,7 @@ private ManageUserPage addNewUser(AdministrationPage administrationPage) { .clickRole("user") .saveUser(); manageUserPage.waitForNotificationsGone(); + manageUserPage.reload(); assertThat(manageUserPage.getUserList()).contains(USERNAME); WiserMessage email = hasEmailRule.getMessages().get(0); assertThat(email.getEnvelopeReceiver()).contains(EMAIL); @@ -143,7 +144,7 @@ private RoleAssignmentsPage adminCreatesRoleAssignmentRule() { .goToAdministration() .goToManageRoleAssignments() .clickMoreActions() - .selectCreateNewRule() + .clickCreateNew() .enterIdentityPattern(USERROLEREGEX) .selectRole("admin") .saveRoleAssignment(); diff --git a/server/gwt-editor/pom.xml b/server/gwt-editor/pom.xml index dc1630709e..a025cadf84 100644 --- a/server/gwt-editor/pom.xml +++ b/server/gwt-editor/pom.xml @@ -4,7 +4,7 @@ org.zanata server - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT gwt-editor gwt-editor diff --git a/server/gwt-shared/pom.xml b/server/gwt-shared/pom.xml index e2dab40854..0e4d21af16 100644 --- a/server/gwt-shared/pom.xml +++ b/server/gwt-shared/pom.xml @@ -6,7 +6,7 @@ server org.zanata - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT gwt-shared diff --git a/server/gwt-test/pom.xml b/server/gwt-test/pom.xml index ca28338659..67e8908f0c 100644 --- a/server/gwt-test/pom.xml +++ b/server/gwt-test/pom.xml @@ -6,7 +6,7 @@ server org.zanata - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT gwt-test diff --git a/server/pom.xml b/server/pom.xml index 412f2d36ff..297bfd59e3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -7,7 +7,7 @@ org.zanata parent - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT ../parent @@ -156,6 +156,18 @@ 1.1 + + commons-validator + commons-validator + 1.6 + + + commons-logging + commons-logging + + + + @@ -1866,20 +1878,6 @@ true - - jboss-public-repository-group - JBoss Public Maven Repository Group - https://repository.jboss.org/nexus/content/groups/public/ - default - - true - never - - - true - never - - arturbosch-code-analysis arturbosch-code-analysis (for detekt, also available in jcenter) diff --git a/server/security-common/pom.xml b/server/security-common/pom.xml index 9ae6d2dfb0..2532056ef3 100644 --- a/server/security-common/pom.xml +++ b/server/security-common/pom.xml @@ -4,7 +4,7 @@ org.zanata server - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT security-common security-common diff --git a/server/services/pom.xml b/server/services/pom.xml index a5c2f3955d..1dda9472cc 100644 --- a/server/services/pom.xml +++ b/server/services/pom.xml @@ -6,7 +6,7 @@ server org.zanata - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT services jar diff --git a/server/services/src/main/java/org/zanata/ApplicationConfiguration.java b/server/services/src/main/java/org/zanata/ApplicationConfiguration.java index 203d0da00a..900798a2a2 100644 --- a/server/services/src/main/java/org/zanata/ApplicationConfiguration.java +++ b/server/services/src/main/java/org/zanata/ApplicationConfiguration.java @@ -256,7 +256,15 @@ public String getServerPath() { if (configuredValue != null) { return configuredValue; } else { - return HttpRequestAndSessionHolder.getDefaultServerPath(); + + String defaultServerPath = + HttpRequestAndSessionHolder.getDefaultServerPath(); + if (defaultServerPath == null) { + log.error("server path is not configured in database and not yet generated from Http request!!!. Admin need to configure it in database"); + // return empty string for now. See ZNTA-2319 + return ""; + } + return defaultServerPath; } } diff --git a/server/services/src/main/java/org/zanata/action/HasUserDetail.java b/server/services/src/main/java/org/zanata/action/HasUserDetail.java index 46697f3606..71dc5b9076 100644 --- a/server/services/src/main/java/org/zanata/action/HasUserDetail.java +++ b/server/services/src/main/java/org/zanata/action/HasUserDetail.java @@ -23,15 +23,15 @@ import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; -import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; import org.zanata.model.validator.EmailDomain; +import org.zanata.model.validator.ZanataEmail; public interface HasUserDetail { String USERNAME_REGEX = "^([a-z\\d][a-z\\d_]*){3,20}$"; int USERNAME_MAX_LENGTH = 20; - @Email + @ZanataEmail @NotEmpty @EmailDomain String getEmail(); diff --git a/server/services/src/main/java/org/zanata/action/InactiveAccountAction.java b/server/services/src/main/java/org/zanata/action/InactiveAccountAction.java index 9719844f13..6b57097c09 100644 --- a/server/services/src/main/java/org/zanata/action/InactiveAccountAction.java +++ b/server/services/src/main/java/org/zanata/action/InactiveAccountAction.java @@ -3,7 +3,6 @@ import java.io.Serializable; import java.util.Date; import org.apache.commons.lang3.StringUtils; -import org.hibernate.validator.constraints.Email; import javax.annotation.PostConstruct; import javax.enterprise.inject.Model; import javax.faces.bean.ViewScoped; @@ -20,6 +19,7 @@ import org.zanata.model.HAccountActivationKey; import org.zanata.model.HPerson; import org.zanata.model.validator.EmailDomain; +import org.zanata.model.validator.ZanataEmail; import org.zanata.security.AuthenticationManager; import org.zanata.security.AuthenticationType; import org.zanata.security.ZanataCredentials; @@ -51,7 +51,7 @@ public class InactiveAccountAction implements Serializable { private AccountActivationKeyDAO accountActivationKeyDAO; @Inject private AuthenticationManager authenticationManager; - @Email + @ZanataEmail @NotDuplicateEmail(message = "This email address is already taken.") @EmailDomain private String email; diff --git a/server/services/src/main/java/org/zanata/action/ServerConfigurationBean.java b/server/services/src/main/java/org/zanata/action/ServerConfigurationBean.java index aa957bd328..fc2716ee84 100644 --- a/server/services/src/main/java/org/zanata/action/ServerConfigurationBean.java +++ b/server/services/src/main/java/org/zanata/action/ServerConfigurationBean.java @@ -31,13 +31,13 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.beanutils.BeanUtils; -import org.hibernate.validator.constraints.Email; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Named; import javax.validation.constraints.Pattern; import org.apache.deltaspike.jpa.api.transaction.Transactional; import org.zanata.action.validator.DomainList; +import org.zanata.model.validator.ZanataEmail; import org.zanata.security.annotations.CheckRole; import org.zanata.ApplicationConfiguration; import org.zanata.action.validator.EmailList; @@ -78,7 +78,7 @@ public class ServerConfigurationBean implements Serializable { private String emailDomain; @EmailList private String adminEmail; - @Email + @ZanataEmail private String fromEmailAddr; @SuppressFBWarnings(value = "SE_BAD_FIELD") private PropertyWithKey fromEmailAddrProperty = diff --git a/server/services/src/main/java/org/zanata/action/UserSettingsAction.java b/server/services/src/main/java/org/zanata/action/UserSettingsAction.java index 099f29f3b3..1dbfc6e811 100644 --- a/server/services/src/main/java/org/zanata/action/UserSettingsAction.java +++ b/server/services/src/main/java/org/zanata/action/UserSettingsAction.java @@ -32,7 +32,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.text.StringEscapeUtils; -import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -48,6 +47,7 @@ import org.zanata.model.security.HCredentials; import org.zanata.model.security.HOpenIdCredentials; import org.zanata.model.validator.EmailDomain; +import org.zanata.model.validator.ZanataEmail; import org.zanata.seam.security.AbstractRunAsOperation; import org.zanata.security.AuthenticationManager; import org.zanata.seam.security.IdentityManager; @@ -85,6 +85,11 @@ public class UserSettingsAction implements Serializable { org.slf4j.LoggerFactory.getLogger(UserSettingsAction.class); private static final long serialVersionUID = 1937219523042662641L; + @Inject + private CredentialsDAO credentialsDAO; + @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "CDI proxies are Serializable") + @Inject + private EntityManager em; @Inject private EmailService emailServiceImpl; @Inject @@ -106,7 +111,7 @@ public class UserSettingsAction implements Serializable { @Inject @Authenticated HAccount authenticatedAccount; - @Email + @ZanataEmail @NotEmpty @EmailDomain private String emailAddress; @@ -227,12 +232,14 @@ public void verifyCredentials(String providerTypeStr) { OpenIdProviderType.valueOf(providerTypeStr); HOpenIdCredentials newCreds = new HOpenIdCredentials(); newCreds.setAccount(authenticatedAccount); + CredentialsCreationCallback callback = new CredentialsCreationCallback( + newCreds, credentialsDAO, facesMessages, em); if (providerType == OpenIdProviderType.Generic) { authenticationManager.openIdAuthenticate(openId, providerType, - new CredentialsCreationCallback(newCreds)); + callback); } else { authenticationManager.openIdAuthenticate(providerType, - new CredentialsCreationCallback(newCreds)); + callback); } } @@ -316,26 +323,24 @@ public void leaveLanguageTeam(String localeId) { } /** - * Callback for credential creation. + * Callback for credential creation. Not a CDI bean, so no @Inject fields. */ private static class CredentialsCreationCallback implements OpenIdAuthCallback, Serializable { - @Inject - private CredentialsDAO credentialsDAO; - @Inject - private FacesMessages facesMessages; - @SuppressFBWarnings("SE_BAD_FIELD") - @Inject - private EntityManager em; private static final long serialVersionUID = 1L; - private HCredentials newCredentials; - - @SuppressWarnings("unused") - public CredentialsCreationCallback() { - } - - private CredentialsCreationCallback(HCredentials newCredentials) { + private final HCredentials newCredentials; + private final CredentialsDAO credentialsDAO; + private final FacesMessages facesMessages; + @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "CDI proxies are Serializable") + private final EntityManager em; + + private CredentialsCreationCallback(HCredentials newCredentials, + CredentialsDAO credentialsDAO, FacesMessages facesMessages, + EntityManager em) { this.newCredentials = newCredentials; + this.credentialsDAO = credentialsDAO; + this.facesMessages = facesMessages; + this.em = em; } @Override @@ -344,7 +349,6 @@ public void afterOpenIdAuth(OpenIdAuthenticationResult result) { if (result.isAuthenticated()) { this.newCredentials.setUser(result.getAuthenticatedId()); this.newCredentials.setEmail(result.getEmail()); - // NB: Seam component injection won't work on callbacks // TODO [CDI] commented out programmatically starting // conversation // Conversation.instance().begin(true, false); // (To retain diff --git a/server/services/src/main/java/org/zanata/action/validator/EmailListValidator.java b/server/services/src/main/java/org/zanata/action/validator/EmailListValidator.java index 9e2509c41e..1ac2dbf2ff 100644 --- a/server/services/src/main/java/org/zanata/action/validator/EmailListValidator.java +++ b/server/services/src/main/java/org/zanata/action/validator/EmailListValidator.java @@ -30,7 +30,8 @@ import javax.validation.Validator; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import org.hibernate.validator.constraints.Email; +import org.zanata.model.validator.ZanataEmail; +import org.zanata.model.validator.ZanataEmailValidator; @ApplicationScoped public class EmailListValidator implements @@ -41,11 +42,15 @@ public class EmailListValidator implements @Inject private Validator validator; + private final ZanataEmailValidator zanataEmailValidator = + new ZanataEmailValidator(); + private static class EmailHolder { EmailHolder(String email) { this.email = email; } - final @Email String email; + final @ZanataEmail + String email; } @Override @@ -57,6 +62,10 @@ public boolean isValid(String s, ConstraintValidatorContext context) { // trim still required to prevent leading whitespace invalidating the // first email address for (String email : s.trim().split("\\s*,\\s*")) { + if (!zanataEmailValidator.isValid(email, context)) { + return false; + } + Set violations = validator.validate(new EmailHolder(email)); if (!violations.isEmpty()) { return false; diff --git a/server/services/src/main/java/org/zanata/adapter/asciidoc/AsciidocParser.java b/server/services/src/main/java/org/zanata/adapter/asciidoc/AsciidocParser.java index d1ebd7efdf..526c40f335 100644 --- a/server/services/src/main/java/org/zanata/adapter/asciidoc/AsciidocParser.java +++ b/server/services/src/main/java/org/zanata/adapter/asciidoc/AsciidocParser.java @@ -30,7 +30,6 @@ import static org.zanata.adapter.asciidoc.AsciidocUtils.ATTR_WHITELIST; import static org.zanata.adapter.asciidoc.AsciidocUtils.NEWLINE; import static org.zanata.adapter.asciidoc.AsciidocUtils.getAdmonitionSkeleton; -import static org.zanata.adapter.asciidoc.AsciidocUtils.getBlockSkeleton; import static org.zanata.adapter.asciidoc.AsciidocUtils.getDocAttribute; import static org.zanata.adapter.asciidoc.AsciidocUtils.getId; import static org.zanata.adapter.asciidoc.AsciidocUtils.getRole; @@ -81,12 +80,13 @@ public void parse() throws ParseException { private void parse(@Nonnull ContentNode node) { Optional id = getId(node.getAttributes()); + Optional role = getRole(node.getAttributes()); if (id.isPresent()) { appendNewLine(); appendNonTranslatable(id.get(), AsciidocUtils.getResId(id.get()), true); } - Optional role = getRole(node.getAttributes()); + if (role.isPresent()) { appendNewLine(); appendNonTranslatable(role.get(), @@ -119,7 +119,7 @@ private void parse(@Nonnull ContentNode node) { eventBuilder.endTextUnit(); appendNewLine(); } else { - appendBlock(); + appendNewLine(); eventBuilder.startTextUnit(); for (String content : block.getLines()) { eventBuilder.addToTextUnit(content + NEWLINE); @@ -129,7 +129,7 @@ private void parse(@Nonnull ContentNode node) { eventBuilder.setTextUnitTranslatable(true); eventBuilder.endTextUnit(); processInnerBlock(block); - appendBlock(); + appendNewLine(); } } } else if(node instanceof Table) { @@ -174,9 +174,8 @@ private void parse(@Nonnull ContentNode node) { private void processInnerBlock(StructuralNode node) { for (StructuralNode innerNode : node.getBlocks()) { - appendBlock(); parse(innerNode); - appendBlock(); + appendNewLine(); } } @@ -196,10 +195,6 @@ private void processTableAttributes( } } - private void appendBlock() { - appendNonTranslatable(getBlockSkeleton(), "", true); - } - private void parseBlockTitle(String title) { GenericSkeleton skeleton = new GenericSkeleton(getBlockTitleSkeleton()); diff --git a/server/services/src/main/java/org/zanata/adapter/asciidoc/AsciidocUtils.java b/server/services/src/main/java/org/zanata/adapter/asciidoc/AsciidocUtils.java index 5635c2c8e8..d541ae18d6 100644 --- a/server/services/src/main/java/org/zanata/adapter/asciidoc/AsciidocUtils.java +++ b/server/services/src/main/java/org/zanata/adapter/asciidoc/AsciidocUtils.java @@ -117,10 +117,6 @@ public final static String getCellEndSkeleton() { return " "; } - public final static String getBlockSkeleton() { - return "****"; - } - public final static boolean isAdmonition( Map attributes) { if (attributes.containsKey("style")) { diff --git a/server/services/src/main/java/org/zanata/dao/AccountDAO.java b/server/services/src/main/java/org/zanata/dao/AccountDAO.java index 0c5719fa21..7005429e4e 100644 --- a/server/services/src/main/java/org/zanata/dao/AccountDAO.java +++ b/server/services/src/main/java/org/zanata/dao/AccountDAO.java @@ -13,7 +13,6 @@ */ package org.zanata.dao; -import java.security.MessageDigest; import java.security.SecureRandom; import java.util.List; import java.util.Optional; @@ -84,33 +83,14 @@ public HAccount getByApiKey(String apikey) { } public void createApiKey(HAccount account) { - String username = account.getUsername(); - String apikey = createSaltedApiKey(username); + String apikey = generateAPIKey(); account.setApiKey(apikey); } - private static String createSaltedApiKey(String username) { - try { - byte[] salt = new byte[16]; - SecureRandom.getInstance("SHA1PRNG").nextBytes(salt); - MessageDigest md5 = MessageDigest.getInstance("MD5"); - byte[] name = username.getBytes("UTF-8"); - - // add salt - byte[] salted = new byte[name.length + salt.length]; - System.arraycopy(name, 0, salted, 0, name.length); - System.arraycopy(salt, 0, salted, name.length, salt.length); - - // generate md5 digest - md5.reset(); - byte[] digest = md5.digest(salted); - - return new String(PasswordUtil.encodeHex(digest)); - - } catch (Exception exc) { - throw new RuntimeException(exc); - } - + protected static String generateAPIKey() { + byte[] bytes = new byte[16]; + new SecureRandom().nextBytes(bytes); + return new String(PasswordUtil.encodeHex(bytes)); } // @SuppressWarnings("unchecked") diff --git a/server/services/src/main/java/org/zanata/dao/ProjectDAO.java b/server/services/src/main/java/org/zanata/dao/ProjectDAO.java index a524d4c07c..46f6f6a91d 100644 --- a/server/services/src/main/java/org/zanata/dao/ProjectDAO.java +++ b/server/services/src/main/java/org/zanata/dao/ProjectDAO.java @@ -36,6 +36,7 @@ import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.TermQuery; +import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.search.jpa.FullTextEntityManager; @@ -104,7 +105,7 @@ public List getOffsetList(int offset, int count, filterOutObsolete, person); StringBuilder sb = new StringBuilder(); - sb.append("select p from HProject p ") + sb.append("select distinct p, UPPER(p.name) from HProject p ") .append(condition) .append("order by UPPER(p.name) asc"); Query q = getSession().createQuery(sb.toString()); @@ -123,8 +124,9 @@ public List getOffsetList(int offset, int count, q.setCacheable(true) .setComment("ProjectDAO.getOffsetList"); @SuppressWarnings("unchecked") - List list = q.list(); - return list; + List list = q.list(); + return list.stream().map(obj -> (HProject) obj[0]) + .collect(Collectors.toList()); } public int getFilterProjectSize(boolean filterOutActive, @@ -134,7 +136,7 @@ public int getFilterProjectSize(boolean filterOutActive, String condition = constructFilterCondition(filterOutActive, filterOutReadOnly, filterOutObsolete, person); - String query = "select count(*) from HProject p " + condition; + String query = "select count(distinct p) from HProject p " + condition; Query q = getSession().createQuery(query); if (person != null) { q.setParameter("person", person); @@ -148,6 +150,10 @@ public int getFilterProjectSize(boolean filterOutActive, return totalCount.intValue(); } + /** + * IMPORTANT: This method will potentially returns duplicate results. + * Caller are required to use 'distinct' to filter out duplication + */ private String constructFilterCondition(boolean filterOutActive, boolean filterOutReadOnly, boolean filterOutObsolete, @Nullable HPerson person) { diff --git a/server/services/src/main/java/org/zanata/rest/JaxRSApplication.java b/server/services/src/main/java/org/zanata/rest/JaxRSApplication.java index 694f5c6e75..e57fcccc52 100644 --- a/server/services/src/main/java/org/zanata/rest/JaxRSApplication.java +++ b/server/services/src/main/java/org/zanata/rest/JaxRSApplication.java @@ -16,7 +16,7 @@ import static java.util.stream.Collectors.toSet; import static java.util.stream.Stream.concat; -@ApplicationPath("/rest") +@ApplicationPath(JaxRSApplication.REST_APP_BASE) @ApplicationScoped public class JaxRSApplication extends javax.ws.rs.core.Application { private static final org.slf4j.Logger log = @@ -24,6 +24,7 @@ public class JaxRSApplication extends javax.ws.rs.core.Application { private static final Set> classes = buildClassesSet(); private static final String PACKAGE_PREFIX = "org.zanata"; + public static final String REST_APP_BASE = "/rest"; @Override public Set> getClasses() { diff --git a/server/services/src/main/java/org/zanata/rest/review/ReviewService.java b/server/services/src/main/java/org/zanata/rest/review/ReviewService.java index 74373782db..61a94e52d9 100644 --- a/server/services/src/main/java/org/zanata/rest/review/ReviewService.java +++ b/server/services/src/main/java/org/zanata/rest/review/ReviewService.java @@ -20,13 +20,11 @@ */ package org.zanata.rest.review; -import java.net.URI; -import java.net.URISyntaxException; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; -import javax.persistence.EntityManager; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -38,16 +36,20 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; +import org.apache.commons.lang3.StringUtils; import org.apache.deltaspike.jpa.api.transaction.Transactional; import org.zanata.dao.ReviewCriteriaDAO; -import org.zanata.exception.ZanataServiceException; import org.zanata.model.ReviewCriteria; +import org.zanata.util.UrlUtil; import org.zanata.webtrans.shared.rest.dto.TransReviewCriteria; import org.zanata.security.annotations.CheckRole; import com.google.common.annotations.VisibleForTesting; +import static org.zanata.model.ReviewCriteria.DESCRIPTION_MAX_LENGTH; + /** * @author Patrick Huang pahuang@redhat.com */ @@ -63,13 +65,17 @@ public class ReviewService { @Context UriInfo uriInfo; + @Inject + private UrlUtil urlUtil; + public ReviewService() { } @VisibleForTesting - protected ReviewService(ReviewCriteriaDAO reviewCriteriaDAO, UriInfo uriInfo) { + protected ReviewService(ReviewCriteriaDAO reviewCriteriaDAO, UriInfo uriInfo, UrlUtil urlUtil) { this.reviewCriteriaDAO = reviewCriteriaDAO; this.uriInfo = uriInfo; + this.urlUtil = urlUtil; } public static TransReviewCriteria fromModel(ReviewCriteria criteria) { @@ -82,17 +88,18 @@ public static TransReviewCriteria fromModel(ReviewCriteria criteria) { @Path("criteria") @Transactional public Response addCriteria(TransReviewCriteria criteria) { + Optional response = validateReviewCriteria(criteria); + if (response.isPresent()) { + return response.get(); + } ReviewCriteria reviewCriteria = new ReviewCriteria(criteria.getPriority(), criteria.isEditable(), criteria.getDescription()); reviewCriteriaDAO.makePersistent(reviewCriteria); - try { - return Response.created(new URI(uriInfo.getRequestUri() + "/" + reviewCriteria.getId())) - .entity(fromModel(reviewCriteria)) - .build(); - } catch (URISyntaxException e) { - throw new ZanataServiceException(e); - } + return Response.created(UriBuilder.fromUri(urlUtil.restPath( + uriInfo.getPath())).path(reviewCriteria.getId().toString()).build()) + .entity(fromModel(reviewCriteria)) + .build(); } @PUT @@ -100,6 +107,10 @@ public Response addCriteria(TransReviewCriteria criteria) { @CheckRole("admin") @Transactional public Response editCriteria(@PathParam("id") Long id, TransReviewCriteria criteria) { + Optional response = validateReviewCriteria(criteria); + if (response.isPresent()) { + return response.get(); + } ReviewCriteria reviewCriteria = reviewCriteriaDAO.findById(id); if (reviewCriteria == null) { @@ -137,4 +148,22 @@ public Response getAllCriteria() { Collectors.toList()); return Response.ok(entity).build(); } + + /** + * Validate DTO in service due to missing hibernate validator in gwt-shared + * DTO. + * + * TODO: move validation to DTO + * + * @param criteria + */ + private Optional validateReviewCriteria(TransReviewCriteria criteria) { + String description = criteria.getDescription(); + if (StringUtils.isBlank(description) || + StringUtils.length(description) > DESCRIPTION_MAX_LENGTH) { + return Optional + .of(Response.status(Response.Status.BAD_REQUEST).build()); + } + return Optional.empty(); + } } diff --git a/server/services/src/main/java/org/zanata/rest/service/AccountService.java b/server/services/src/main/java/org/zanata/rest/service/AccountService.java index b7d839a4ee..722781cc9f 100644 --- a/server/services/src/main/java/org/zanata/rest/service/AccountService.java +++ b/server/services/src/main/java/org/zanata/rest/service/AccountService.java @@ -31,6 +31,7 @@ import org.zanata.model.HLocale; import org.zanata.model.HPerson; import org.zanata.rest.dto.Account; +import org.zanata.util.UrlUtil; @RequestScoped @Named("accountService") @@ -62,6 +63,8 @@ public class AccountService implements AccountResource { private ZanataIdentity identity; @Inject private Session session; + @Inject + private UrlUtil urlUtil; @Override public Response get() { @@ -87,7 +90,7 @@ public Response put(Account account) { if (hAccount == null) { // creating operation = "insert"; - response = Response.created(uri.getAbsolutePath()); + response = Response.created(urlUtil.restPathURI(uri.getPath())); hAccount = new HAccount(); HPerson person = new HPerson(); person.setAccount(hAccount); diff --git a/server/services/src/main/java/org/zanata/rest/service/AsyncProcessService.java b/server/services/src/main/java/org/zanata/rest/service/AsyncProcessService.java index 452c7ddebd..ca4962f5cd 100644 --- a/server/services/src/main/java/org/zanata/rest/service/AsyncProcessService.java +++ b/server/services/src/main/java/org/zanata/rest/service/AsyncProcessService.java @@ -78,6 +78,9 @@ public class AsyncProcessService { @Inject private ZanataIdentity identity; + // NOTE: uriInfo usage in this class is limited to return relative path. + // The zanata client will be responsible to construct the full url. + // therefore this class do not suffer from proxy rewrite url problem (ZNTA-2273) @SuppressFBWarnings("SE_BAD_FIELD") @Context private UriInfo uriInfo; diff --git a/server/services/src/main/java/org/zanata/rest/service/ProjectService.java b/server/services/src/main/java/org/zanata/rest/service/ProjectService.java index da29d71170..c2c14f4bbf 100644 --- a/server/services/src/main/java/org/zanata/rest/service/ProjectService.java +++ b/server/services/src/main/java/org/zanata/rest/service/ProjectService.java @@ -44,6 +44,7 @@ import org.zanata.security.ZanataIdentity; import org.zanata.service.impl.WebhookServiceImpl; import org.zanata.util.GlossaryUtil; +import org.zanata.util.UrlUtil; import org.zanata.webhook.events.ProjectMaintainerChangedEvent; import com.google.common.base.Objects; @@ -85,6 +86,9 @@ public class ProjectService implements ProjectResource { @Inject ETagUtils eTagUtils; + @Inject + UrlUtil urlUtil; + @Nonnull public String getProjectSlug() { return projectSlug; @@ -135,7 +139,7 @@ public Response put(Project project) { // pre-emptive entity permission check identity.checkPermission(hProject, "insert"); - response = Response.created(uri.getAbsolutePath()); + response = Response.created(urlUtil.restPathURI(uri.getPath())); } else if (Objects.equal(hProject.getStatus(), OBSOLETE)) { // Project is obsolete return Response.status(Status.FORBIDDEN) diff --git a/server/services/src/main/java/org/zanata/rest/service/ProjectVersionService.java b/server/services/src/main/java/org/zanata/rest/service/ProjectVersionService.java index 5285139409..6dfc693613 100644 --- a/server/services/src/main/java/org/zanata/rest/service/ProjectVersionService.java +++ b/server/services/src/main/java/org/zanata/rest/service/ProjectVersionService.java @@ -61,6 +61,7 @@ import org.zanata.service.ConfigurationService; import org.zanata.service.LocaleService; import org.zanata.util.HttpUtil; +import org.zanata.util.UrlUtil; import org.zanata.webtrans.shared.model.DocumentId; import org.zanata.webtrans.shared.rest.dto.InternalTMSource; import org.zanata.webtrans.shared.search.FilterConstraints; @@ -108,6 +109,8 @@ public class ProjectVersionService implements ProjectVersionResource { private UriInfo uri; @Inject private TransMemoryMergeManager transMemoryMergeManager; + @Inject + private UrlUtil urlUtil; @Override public Response head(@PathParam("projectSlug") String projectSlug, @@ -156,7 +159,7 @@ public Response put(@PathParam("projectSlug") String projectSlug, // pre-emptive entity permission check // identity.checkWorkspaceAction(hProject, "add-iteration"); identity.checkPermission(hProjectVersion, "insert"); - response = Response.created(uri.getAbsolutePath()); + response = Response.created(urlUtil.restPathURI(uri.getPath())); changed = true; } else if (Objects.equal(hProjectVersion.getStatus(), OBSOLETE)) { // Iteration is Obsolete @@ -567,7 +570,7 @@ protected ProjectVersionService(final TextFlowDAO textFlowDAO, final ConfigurationService configurationServiceImpl, final ZanataIdentity identity, final UserService userService, final ApplicationConfiguration applicationConfiguration, - final UriInfo uri) { + final UriInfo uri, final UrlUtil urlUtil) { this.textFlowDAO = textFlowDAO; this.documentDAO = documentDAO; this.projectDAO = projectDAO; @@ -581,5 +584,6 @@ protected ProjectVersionService(final TextFlowDAO textFlowDAO, this.userService = userService; this.applicationConfiguration = applicationConfiguration; this.uri = uri; + this.urlUtil = urlUtil; } } diff --git a/server/services/src/main/java/org/zanata/rest/service/SourceDocResourceService.java b/server/services/src/main/java/org/zanata/rest/service/SourceDocResourceService.java index 9b5452bbca..055f9969cb 100644 --- a/server/services/src/main/java/org/zanata/rest/service/SourceDocResourceService.java +++ b/server/services/src/main/java/org/zanata/rest/service/SourceDocResourceService.java @@ -64,6 +64,7 @@ import org.zanata.security.ZanataIdentity; import org.zanata.service.DocumentService; import org.zanata.service.LocaleService; +import org.zanata.util.UrlUtil; /** * @author Carlos Munoz @@ -110,6 +111,9 @@ public class SourceDocResourceService implements SourceDocResource { @Inject private ZanataIdentity identity; + @Inject + private UrlUtil urlUtil; + @Override public Response head() { HProjectIteration hProjectIteration = retrieveAndCheckIteration(false); @@ -257,7 +261,7 @@ public Response putResourceWithDocId(Resource resource, String docId, docId); if (document == null || document.isObsolete()) { response = Response.created( - UriBuilder.fromUri(uri.getAbsolutePath()) + UriBuilder.fromUri(urlUtil.restPath(uri.getPath())) .queryParam("docId", docId).build()); } else { response = Response.ok(); diff --git a/server/services/src/main/java/org/zanata/rest/service/TranslatedDocResourceService.java b/server/services/src/main/java/org/zanata/rest/service/TranslatedDocResourceService.java index b0ccc2668b..0086be9493 100644 --- a/server/services/src/main/java/org/zanata/rest/service/TranslatedDocResourceService.java +++ b/server/services/src/main/java/org/zanata/rest/service/TranslatedDocResourceService.java @@ -101,9 +101,7 @@ public class TranslatedDocResourceService implements TranslatedDocResource { @Context @SuppressFBWarnings("SE_BAD_FIELD") private Request request; - @Context - @SuppressFBWarnings("SE_BAD_FIELD") - private UriInfo uri; + @Inject private ZanataIdentity identity; @Inject diff --git a/server/services/src/main/java/org/zanata/security/AnonymousAccessControlPhaseListener.java b/server/services/src/main/java/org/zanata/security/AnonymousAccessControlPhaseListener.java index c33971c8c2..68f9ada275 100644 --- a/server/services/src/main/java/org/zanata/security/AnonymousAccessControlPhaseListener.java +++ b/server/services/src/main/java/org/zanata/security/AnonymousAccessControlPhaseListener.java @@ -43,6 +43,8 @@ @JsfPhaseListener public class AnonymousAccessControlPhaseListener implements PhaseListener { private static final long serialVersionUID = 7857787462325457761L; + private static final String ERROR_PATH = "/error/"; + private Provider allowAnonymousAccessProvider; private ZanataIdentity identity; @@ -67,7 +69,7 @@ public void afterPhase(PhaseEvent phaseEvent) { @Override public void beforePhase(PhaseEvent phaseEvent) { - if (!requestingPageIsSignInOrRegister() && + if (!requestingUnprotectedPage() && anonymousAccessIsNotAllowed()) { throw new NotLoggedInException(); } @@ -77,11 +79,12 @@ private boolean anonymousAccessIsNotAllowed() { return !allowAnonymousAccessProvider.get() && !identity.isLoggedIn(); } - private boolean requestingPageIsSignInOrRegister() { + private boolean requestingUnprotectedPage() { // the request URI will be the internal URI String contextPath = request.getContextPath(); return request.getRequestURI().startsWith(contextPath + "/account/") || - request.getRequestURI().startsWith(contextPath + "/public/"); + request.getRequestURI().startsWith(contextPath + "/public/") || + request.getRequestURI().startsWith(contextPath + ERROR_PATH); } @Override diff --git a/server/services/src/main/java/org/zanata/security/AuthenticationManager.java b/server/services/src/main/java/org/zanata/security/AuthenticationManager.java index 9a20f8f94a..23e22bc81c 100644 --- a/server/services/src/main/java/org/zanata/security/AuthenticationManager.java +++ b/server/services/src/main/java/org/zanata/security/AuthenticationManager.java @@ -92,6 +92,9 @@ public class AuthenticationManager implements Serializable { private boolean saml2Enabled; @Inject private SamlIdentity samlIdentity; + @Inject + private SamlAccountService samlAccountService; + /** * Logs in a user using a specified authentication type. @@ -182,7 +185,8 @@ && isAccountEnabledAndActivated()) { public void ssoLogin() { if (saml2Enabled) { samlIdentity.authenticate(); - + // here we try to auto merge account if email matches + samlAccountService.tryMergeToExistingAccount(); if (!isNewUser() && !isAuthenticatedAccountWaitingForActivation() && isAccountEnabledAndActivated()) { samlIdentity.login(); diff --git a/server/services/src/main/java/org/zanata/security/SamlAccountService.kt b/server/services/src/main/java/org/zanata/security/SamlAccountService.kt index a70246a942..941339a0c8 100644 --- a/server/services/src/main/java/org/zanata/security/SamlAccountService.kt +++ b/server/services/src/main/java/org/zanata/security/SamlAccountService.kt @@ -30,6 +30,7 @@ import org.zanata.model.security.HSaml2Credentials import org.zanata.security.annotations.SAML import org.zanata.security.annotations.SAMLAttribute import org.zanata.security.annotations.SAMLAttribute.AttributeName.EMAIL +import java.io.Serializable import java.security.Principal import javax.enterprise.context.RequestScoped import javax.inject.Inject @@ -42,7 +43,7 @@ class SamlAccountService @Inject constructor( @SAMLAttribute(EMAIL) private val email: String, @SAML private val principal: Principal, private val accountDAO: AccountDAO, - private val credentialsDAO: CredentialsDAO) { + private val credentialsDAO: CredentialsDAO) : Serializable { private fun isFirstSignIn(uniqueId: String): Boolean = credentialsDAO.findByUser(uniqueId) == null @@ -61,5 +62,6 @@ class SamlAccountService @Inject constructor( companion object { private val log: Logger = LoggerFactory.getLogger(SamlAccountService::class.java) + private const val serialVersionUID: Long = 1L } } diff --git a/server/services/src/main/java/org/zanata/security/UserRedirectBean.java b/server/services/src/main/java/org/zanata/security/UserRedirectBean.java index b80dba7f16..3e86b383fe 100644 --- a/server/services/src/main/java/org/zanata/security/UserRedirectBean.java +++ b/server/services/src/main/java/org/zanata/security/UserRedirectBean.java @@ -24,6 +24,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.List; import javax.enterprise.context.SessionScoped; import javax.enterprise.event.Observes; @@ -32,6 +33,7 @@ import javax.inject.Named; import javax.servlet.http.HttpServletRequest; +import com.google.common.collect.ImmutableList; import org.zanata.events.NotLoggedInEvent; import org.zanata.servlet.annotations.ContextPath; import org.zanata.util.Synchronized; @@ -55,15 +57,20 @@ public class UserRedirectBean implements Serializable { private static final String HOME_PATH = "/"; private static final String REGISTER_PATH = "/register"; - private static final String ERROR_PATH = "/error/"; private static final String LOGIN_PATH = "/sign_in"; - /** - * - */ private static final long serialVersionUID = 1L; private final static String ENCODING = "UTF-8"; + // url for error pages before redirection + private static final ImmutableList ERROR_PAGES_URL = + ImmutableList.builder() + .add("/404") + .add("/error/home") + .add("/error/missing_entity") + .add("/error/viewexpiredexception") + .build(); + @Inject @ContextPath private String contextPath; @Inject @@ -136,7 +143,7 @@ public boolean isRedirectToHome() { } public boolean isRedirectToError() { - return isRedirectTo(ERROR_PATH); + return isRedirectTo(ERROR_PAGES_URL); } public boolean isRedirectToRegister() { @@ -172,10 +179,21 @@ private String getPath(String localUrl) { return path; } + private boolean isRedirectTo(List pathWithoutContext) { + if (isRedirect()) { + String redirectPath = getPath(getUrl()); + for (String path : pathWithoutContext) { + if (redirectPath.equals(path)) { + return true; + } + } + } + return false; + } + private boolean isRedirectTo(String pathWithoutContext) { if (isRedirect()) { - String redirectingUrl = getUrl(); - String redirectPath = getPath(redirectingUrl); + String redirectPath = getPath(getUrl()); if (redirectPath.equals(pathWithoutContext)) { return true; } diff --git a/server/services/src/main/java/org/zanata/service/impl/TransMemoryMergeServiceImpl.java b/server/services/src/main/java/org/zanata/service/impl/TransMemoryMergeServiceImpl.java index 067b2018c1..0145b00cd3 100644 --- a/server/services/src/main/java/org/zanata/service/impl/TransMemoryMergeServiceImpl.java +++ b/server/services/src/main/java/org/zanata/service/impl/TransMemoryMergeServiceImpl.java @@ -190,6 +190,9 @@ public List executeMerge( targetLocale, UNTRANSLATED_FILTER, index, BATCH_SIZE); int processedSize = textFlowsBatch.size(); + if (processedSize == 0) { + break; + } index = index + processedSize; asyncTaskHandle.increaseProgress(processedSize); diff --git a/server/services/src/main/java/org/zanata/servlet/SAMLFilter.kt b/server/services/src/main/java/org/zanata/servlet/SAMLFilter.kt index 45253944c7..5ec9c212f7 100644 --- a/server/services/src/main/java/org/zanata/servlet/SAMLFilter.kt +++ b/server/services/src/main/java/org/zanata/servlet/SAMLFilter.kt @@ -22,7 +22,6 @@ package org.zanata.servlet import com.google.common.annotations.VisibleForTesting import org.zanata.security.AuthenticationManager -import org.zanata.security.SamlAccountService import org.zanata.security.SamlAttributes import org.zanata.util.UrlUtil import java.io.IOException @@ -49,16 +48,13 @@ class SAMLFilter() : Filter { private lateinit var urlUtil: UrlUtil @Inject private lateinit var samlAttributes: SamlAttributes - @Inject - private lateinit var samlAccountService: SamlAccountService @VisibleForTesting constructor(authenticationManager: AuthenticationManager, urlUtil: UrlUtil, - samlAttributes: SamlAttributes, samlAccountService: SamlAccountService) : this() { + samlAttributes: SamlAttributes) : this() { this.authenticationManager = authenticationManager this.urlUtil = urlUtil this.samlAttributes = samlAttributes - this.samlAccountService = samlAccountService } @Throws(ServletException::class) @@ -70,8 +66,6 @@ class SAMLFilter() : Filter { if (request is HttpServletRequest) { if (samlAttributes.isAuthenticated) { authenticationManager.ssoLogin() - // here we try to auto merge account if email matches - samlAccountService.tryMergeToExistingAccount() performRedirection(response as HttpServletResponse) return } diff --git a/server/services/src/main/java/org/zanata/servlet/UrlRewriteConfig.java b/server/services/src/main/java/org/zanata/servlet/UrlRewriteConfig.java index 81a001a83c..c113ec8667 100644 --- a/server/services/src/main/java/org/zanata/servlet/UrlRewriteConfig.java +++ b/server/services/src/main/java/org/zanata/servlet/UrlRewriteConfig.java @@ -137,7 +137,7 @@ public Configuration getConfiguration(final ServletContext context) { .addRule(Join.path("/dashboard/{section}").to("/dashboard/home.xhtml")) .where("section").matches(".*") - .addRule(Join.path("/error/").to("/error.xhtml")) + .addRule(Join.path("/error/").to("/error/home.xhtml")) .addRule(Join.pathNonBinding("/error/{path}").to("/error/{path}.xhtml")) .addRule(Join.path("/iteration/view/{projectSlug}/{iterationSlug}").to("/iteration/view.xhtml")) @@ -182,7 +182,7 @@ public Configuration getConfiguration(final ServletContext context) { .addRule(Join.path("/webtrans/Application.html").to("/webtrans/Application.xhtml")).when(Direction.isInbound()) .addRule(Join.path("/webtrans/translate").to("/webtrans/Application.xhtml")) - .addRule(Join.path("/404").to("/404.xhtml")) + .addRule(Join.path("/404").to("/error/404.xhtml")) // OAuth authorization .addRule(Join.path("/oauth/").to("/oauth/home.xhtml")) ; diff --git a/server/services/src/main/java/org/zanata/util/UrlUtil.java b/server/services/src/main/java/org/zanata/util/UrlUtil.java index d81d81d50a..e8c15123c2 100644 --- a/server/services/src/main/java/org/zanata/util/UrlUtil.java +++ b/server/services/src/main/java/org/zanata/util/UrlUtil.java @@ -29,19 +29,25 @@ import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + import javax.enterprise.context.RequestScoped; import javax.faces.context.FacesContext; -import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang3.StringUtils; import javax.inject.Inject; import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; import javax.validation.constraints.NotNull; +import javax.ws.rs.core.UriBuilder; +import org.apache.commons.lang3.StringUtils; import org.apache.deltaspike.core.spi.scope.window.WindowContext; import org.apache.http.client.utils.URIBuilder; import org.zanata.common.LocaleId; +import org.zanata.rest.JaxRSApplication; import org.zanata.servlet.annotations.ContextPath; import org.zanata.servlet.annotations.ServerPath; +import com.google.common.collect.Lists; /** * Get the URL for the current page in URL encoded format for use in the query @@ -56,21 +62,26 @@ public class UrlUtil implements Serializable { private static final long serialVersionUID = 1L; private static final String ENCODING = "UTF-8"; - @Inject - @ServerPath private String serverPath; - @Inject - @ContextPath private String contextPath; - @Inject private WindowContext windowContext; - @Inject - @Named("dswidQuery") private String dswidQuery; - @Inject - @Named("dswidParam") private String dswidParam; + public UrlUtil() { + } + + @Inject + public UrlUtil(@ServerPath String serverPath, @ContextPath String contextPath, + WindowContext windowContext, @Named("dswidQuery") String dswidQuery, + @Named("dswidParam") String dswidParam) { + this.serverPath = serverPath; + this.contextPath = contextPath; + this.windowContext = windowContext; + this.dswidQuery = dswidQuery; + this.dswidParam = dswidParam; + } + /** * Get the local url part, including context path, for the given page * request. @@ -354,4 +365,35 @@ public String inactiveAccountPage() { return contextPath + "/account/inactive" + dswidQuery; } + /** + * This helper method should be used to construct REST url. You can inject + * UriInfo and call the getPath() method to get a resource relative path. + * + * @param relativePath + * a REST resource relative path (after + * protocol://server:port/rest/) + * @return absolute path to the REST resource + */ + public String restPath(String relativePath) { + return joinPaths(joinPaths(serverPath, JaxRSApplication.REST_APP_BASE), relativePath); + } + + /** + * + * @see UrlUtil#restPath(java.lang.String) + */ + public URI restPathURI(String relativePath) { + return UriBuilder.fromUri(restPath(relativePath)).build(); + } + + static String joinPaths(String one, String two) { + if (one.endsWith("/") && two.startsWith("/")) { + return one + two.substring(1); + } else if (!one.endsWith("/") && !two.startsWith("/")) { + return one + "/" + two; + } + // either one ends with / or two start with / + return one + two; + } + } diff --git a/server/services/src/main/resources/db/changelogs/db.changelog-4.4.xml b/server/services/src/main/resources/db/changelogs/db.changelog-4.4.xml index b7922d7bb6..8a63017a18 100644 --- a/server/services/src/main/resources/db/changelogs/db.changelog-4.4.xml +++ b/server/services/src/main/resources/db/changelogs/db.changelog-4.4.xml @@ -40,6 +40,10 @@ + + + diff --git a/server/services/src/test/java/org/zanata/dao/AccountDAOTest.java b/server/services/src/test/java/org/zanata/dao/AccountDAOTest.java new file mode 100644 index 0000000000..4cbba9cbef --- /dev/null +++ b/server/services/src/test/java/org/zanata/dao/AccountDAOTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2018, Red Hat, Inc. and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.zanata.dao; + +import org.dbunit.operation.DatabaseOperation; +import org.junit.Before; +import org.junit.Test; +import org.zanata.ZanataDbunitJpaTest; +import org.zanata.model.HAccount; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AccountDAOTest extends ZanataDbunitJpaTest { + + private AccountDAO accountDAO; + + @Before + public void setup() { + accountDAO = new AccountDAO(getSession()); + } + + @Override + protected void prepareDBUnitOperations() { + beforeTestOperations.add(new DataSetOperation( + "org/zanata/test/model/ClearAllTables.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + beforeTestOperations.add(new DataSetOperation( + "org/zanata/test/model/AccountData.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + } + + @Test + public void createAPITest() { + HAccount account = accountDAO.getByUsername("demo"); + String apiKey = account.getApiKey(); + accountDAO.createApiKey(account); + assertThat(account.getApiKey()).isNotEqualTo(apiKey); + } + + @Test + public void generateAPIKeyTest() { + String apiKey = AccountDAO.generateAPIKey(); + assertThat(apiKey).isNotBlank().hasSize(32); + } +} diff --git a/server/services/src/test/java/org/zanata/dao/ProjectDAOTest.java b/server/services/src/test/java/org/zanata/dao/ProjectDAOTest.java index e050b4a48a..004caed7dd 100644 --- a/server/services/src/test/java/org/zanata/dao/ProjectDAOTest.java +++ b/server/services/src/test/java/org/zanata/dao/ProjectDAOTest.java @@ -110,6 +110,8 @@ public void getOffsetList() { List projects = dao.getOffsetList(-1, -1, false, false, false); assertThat(projects.size()).isEqualTo(4); + int size = dao.getFilterProjectSize(false, false, false); + assertThat(projects.size()).isEqualTo(size); } @Test @@ -118,5 +120,7 @@ public void getOffsetListPrivateProject() { dao.setAuthenticatedAccount(person.getAccount()); List projects = dao.getOffsetList(-1, -1, false, false, false); assertThat(projects).hasSize(5); + int size = dao.getFilterProjectSize(false, false, false); + assertThat(projects.size()).isEqualTo(size); } } diff --git a/server/services/src/test/java/org/zanata/rest/review/ReviewServiceTest.java b/server/services/src/test/java/org/zanata/rest/review/ReviewServiceTest.java index b60da8041b..38066d4554 100644 --- a/server/services/src/test/java/org/zanata/rest/review/ReviewServiceTest.java +++ b/server/services/src/test/java/org/zanata/rest/review/ReviewServiceTest.java @@ -17,19 +17,22 @@ import org.zanata.common.IssuePriority; import org.zanata.dao.ReviewCriteriaDAO; import org.zanata.model.ReviewCriteria; +import org.zanata.util.UrlUtil; import org.zanata.webtrans.shared.rest.dto.TransReviewCriteria; public class ReviewServiceTest extends ZanataJpaTest { private static final String DESCRIPTION = "bad grammar"; + private static final String SERVER_PATH = "https://localhost/zanata/"; private ReviewService reviewService; @Mock private UriInfo uriInfo; + private UrlUtil urlUtil = new UrlUtil(SERVER_PATH, "/zanata", null, null, null); @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - reviewService = new ReviewService(new ReviewCriteriaDAO(getSession()), uriInfo); + reviewService = new ReviewService(new ReviewCriteriaDAO(getSession()), uriInfo, urlUtil); } @Test @@ -49,14 +52,19 @@ public void canGetAllCriteria() { @Test public void canAddNewEntry() throws Exception { - when(uriInfo.getRequestUri()) - .thenReturn(new URI("http://example.com/rest")); + when(uriInfo.getPath()) + .thenReturn("criteria"); + URI baseUri = new URI(SERVER_PATH +"rest/"); + when(uriInfo.getBaseUri()) + .thenReturn(baseUri); TransReviewCriteria dto = new TransReviewCriteria(null, IssuePriority.Critical, DESCRIPTION, false); Response response = reviewService.addCriteria(dto); TransReviewCriteria entity = (TransReviewCriteria) response.getEntity(); assertThat(entity.getDescription()).isEqualTo(DESCRIPTION); assertThat(entity.getId()).isNotNull(); + assertThat(response.getLocation().toString()) + .isEqualTo(SERVER_PATH + "rest/criteria/" + entity.getId()); } @Test diff --git a/server/services/src/test/java/org/zanata/rest/service/ProjectVersionServiceUnitTest.java b/server/services/src/test/java/org/zanata/rest/service/ProjectVersionServiceUnitTest.java index 2a7ce1a599..f143aa56a9 100644 --- a/server/services/src/test/java/org/zanata/rest/service/ProjectVersionServiceUnitTest.java +++ b/server/services/src/test/java/org/zanata/rest/service/ProjectVersionServiceUnitTest.java @@ -4,6 +4,7 @@ import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; import org.assertj.core.util.Lists; import org.junit.Before; @@ -27,6 +28,7 @@ import org.zanata.rest.dto.ProjectIteration; import org.zanata.rest.dto.resource.ResourceMeta; import org.zanata.rest.editor.service.UserService; +import org.zanata.util.UrlUtil; import org.zanata.webtrans.shared.search.FilterConstraints; import org.zanata.service.LocaleService; import org.zanata.webtrans.server.rpc.GetTransUnitsNavigationService; @@ -57,6 +59,8 @@ public class ProjectVersionServiceUnitTest { private UserService userService; @Mock private ApplicationConfiguration applicationConfiguration; + @Mock private UriInfo uri; + @Mock private UrlUtil urlUtil; @Before public void setUp() throws Exception { @@ -64,7 +68,8 @@ public void setUp() throws Exception { service = new ProjectVersionService(textFlowDAO, documentDAO, null, projectIterationDAO, localeService, request, etagUtil, - new ResourceUtils(), null, null, userService, applicationConfiguration, null); + new ResourceUtils(), null, null, userService, + applicationConfiguration, uri, urlUtil); } diff --git a/server/services/src/test/java/org/zanata/security/AnonymousAccessControlPhaseListenerTest.java b/server/services/src/test/java/org/zanata/security/AnonymousAccessControlPhaseListenerTest.java index 6eb9d2469c..d0f8319844 100644 --- a/server/services/src/test/java/org/zanata/security/AnonymousAccessControlPhaseListenerTest.java +++ b/server/services/src/test/java/org/zanata/security/AnonymousAccessControlPhaseListenerTest.java @@ -47,7 +47,7 @@ public void anonymousAccessToPageUnderPublicIsAllowed() { @Test public void anonymousAccessToUnprotectedPageIsAllowed() { - when(request.getRequestURI()).thenReturn("404.xhtml"); + when(request.getRequestURI()).thenReturn("/error/404.xhtml"); when(anonymousAccessProvider.get()).thenReturn(true); checker.beforePhase(phaseEvent); @@ -56,7 +56,7 @@ public void anonymousAccessToUnprotectedPageIsAllowed() { @Test public void loggedInAccessToProtectedPageIsAllowed() { - when(request.getRequestURI()).thenReturn("404.xhtml"); + when(request.getRequestURI()).thenReturn("/error/404.xhtml"); when(anonymousAccessProvider.get()).thenReturn(false); when(identity.isLoggedIn()).thenReturn(true); @@ -66,7 +66,7 @@ public void loggedInAccessToProtectedPageIsAllowed() { @Test public void anonymousAccessToProtectedPageIsDenied() { - when(request.getRequestURI()).thenReturn("404.xhtml"); + when(request.getRequestURI()).thenReturn("/dashboard/home.xhtml"); when(anonymousAccessProvider.get()).thenReturn(false); when(identity.isLoggedIn()).thenReturn(false); @@ -75,4 +75,20 @@ public void anonymousAccessToProtectedPageIsDenied() { } + @Test + public void anonymousAccessToErrorPage() { + when(request.getRequestURI()).thenReturn("/error/404.xhtml"); + when(anonymousAccessProvider.get()).thenReturn(true); + when(identity.isLoggedIn()).thenReturn(false); + checker.beforePhase(phaseEvent); + } + + @Test + public void anonymousAccessToErrorPage2() { + when(request.getRequestURI()).thenReturn("/error/index.xhtml"); + when(anonymousAccessProvider.get()).thenReturn(true); + when(identity.isLoggedIn()).thenReturn(false); + checker.beforePhase(phaseEvent); + } + } diff --git a/server/services/src/test/java/org/zanata/security/AuthenticationManagerTest.kt b/server/services/src/test/java/org/zanata/security/AuthenticationManagerTest.kt index 4628355af8..7eaff3d0af 100644 --- a/server/services/src/test/java/org/zanata/security/AuthenticationManagerTest.kt +++ b/server/services/src/test/java/org/zanata/security/AuthenticationManagerTest.kt @@ -70,6 +70,8 @@ class AuthenticationManagerTest { private lateinit var samlIdentity: SamlIdentity @Inject private lateinit var authenticationManager: AuthenticationManager + @Mock + @Produces private lateinit var samlAccountService: SamlAccountService @Test @InRequestScope @@ -106,4 +108,18 @@ class AuthenticationManagerTest { verify(identity, never()).addRole(anyString()) } + + @Test + @InRequestScope + fun onSSOLogin() { + given(identity.credentials).willReturn(credentials) + val username = "admin" + given(credentials.username).willReturn(username) + given(identityStore.isUserEnabled(username)).willReturn(true) + + authenticationManager.ssoLogin() + + verify(samlIdentity).authenticate() + verify(samlAccountService).tryMergeToExistingAccount() + } } diff --git a/server/services/src/test/java/org/zanata/servlet/SAMLFilterTest.kt b/server/services/src/test/java/org/zanata/servlet/SAMLFilterTest.kt index 73e4ff49c0..f5aff6f147 100644 --- a/server/services/src/test/java/org/zanata/servlet/SAMLFilterTest.kt +++ b/server/services/src/test/java/org/zanata/servlet/SAMLFilterTest.kt @@ -12,7 +12,6 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations import org.zanata.security.AuthenticationManager -import org.zanata.security.SamlAccountService import org.zanata.security.SamlAttributes import org.zanata.util.UrlUtil import javax.servlet.FilterChain @@ -35,13 +34,11 @@ class SAMLFilterTest { private lateinit var authenticationManager: AuthenticationManager @Mock private lateinit var urlUtil: UrlUtil - @Mock - private lateinit var samlAccountService: SamlAccountService @Before fun setUp() { MockitoAnnotations.initMocks(this) - filter = SAMLFilter(authenticationManager, urlUtil, samlAttributes, samlAccountService) + filter = SAMLFilter(authenticationManager, urlUtil, samlAttributes) } @@ -76,6 +73,5 @@ class SAMLFilterTest { filter.doFilter(request, response, chain) verify(response).sendRedirect("/dashboard") - verify(samlAccountService).tryMergeToExistingAccount() } } diff --git a/server/services/src/test/java/org/zanata/util/UrlUtilTest.java b/server/services/src/test/java/org/zanata/util/UrlUtilTest.java index 9bcc57f9c3..7c31851ee5 100644 --- a/server/services/src/test/java/org/zanata/util/UrlUtilTest.java +++ b/server/services/src/test/java/org/zanata/util/UrlUtilTest.java @@ -90,4 +90,12 @@ public void getLocalUrlWithAttributeTest() { String localUrl = urlUtil.getLocalUrl(request); assertThat(localUrl).contains(queryString).contains(contextPath); } + + @Test + public void canJoinPaths() { + assertThat(UrlUtil.joinPaths("a/", "/b")).isEqualTo("a/b"); + assertThat(UrlUtil.joinPaths("a", "b")).isEqualTo("a/b"); + assertThat(UrlUtil.joinPaths("a", "/b")).isEqualTo("a/b"); + assertThat(UrlUtil.joinPaths("a/", "b")).isEqualTo("a/b"); + } } diff --git a/server/services/src/test/resources/META-INF/persistence.xml b/server/services/src/test/resources/META-INF/persistence.xml index fdda128b27..269d4b8090 100644 --- a/server/services/src/test/resources/META-INF/persistence.xml +++ b/server/services/src/test/resources/META-INF/persistence.xml @@ -117,7 +117,7 @@ + value="native" /> diff --git a/server/zanata-frontend/.gitignore b/server/zanata-frontend/.gitignore index d295aaf9f5..da4618d648 100644 --- a/server/zanata-frontend/.gitignore +++ b/server/zanata-frontend/.gitignore @@ -11,12 +11,12 @@ dist/ coverage/ # generated css -src/frontend/app/styles/atomic.css +src/app/styles/atomic.css # generated frontend icons -src/frontend/app/components/Icon/list.js -src/frontend/app/components/Icons/icons.svg -src/frontend/app/components/Icons/index.jsx +src/app/components/Icon/list.js +src/app/components/Icons/icons.svg +src/app/components/Icons/index.jsx # files generated by `make storybook-static`, should never be checked in storybook-static diff --git a/server/zanata-frontend/README.md b/server/zanata-frontend/README.md index 6a4214af99..e13140ca8f 100644 --- a/server/zanata-frontend/README.md +++ b/server/zanata-frontend/README.md @@ -3,7 +3,7 @@ This module contains: User profile page, Glossary page, and Zanata side menu bar. - [Zanata alpha editor](./src/editor/README.md) -- [Explore page, User profile page, Glossary page, and side menu bar](./src/frontend/README.md) +- [Explore page, User profile page, Glossary page, and side menu bar](./src/README.md) Navigate to `src`, run `make install` diff --git a/server/zanata-frontend/pom.xml b/server/zanata-frontend/pom.xml index d1da0e2e60..833dff6616 100644 --- a/server/zanata-frontend/pom.xml +++ b/server/zanata-frontend/pom.xml @@ -4,7 +4,7 @@ org.zanata server - 4.4.0-SNAPSHOT + 4.5.0-SNAPSHOT zanata-frontend Zanata frontend @@ -21,7 +21,7 @@ v8.1.3 - v1.1.0 + v1.3.2 ${download.dir}/zanata/node-${node.version}-yarn-${yarn.version} @@ -30,7 +30,7 @@ UTF-8 - ${project.basedir}/src/frontend + ${project.basedir}/src draft @@ -61,7 +61,7 @@ yet. This ensures that JS test failures will break the build. TODO get Jest to output junit.xml and remove this: --> - false + false @@ -132,7 +132,7 @@ ${bundle.dest} - src/frontend/dist + src/dist editor.*.cache.js editor.*.cache.css editor.*.cache.css.map diff --git a/server/zanata-frontend/src/frontend/.csscomb.json b/server/zanata-frontend/src/.csscomb.json similarity index 100% rename from server/zanata-frontend/src/frontend/.csscomb.json rename to server/zanata-frontend/src/.csscomb.json diff --git a/server/zanata-frontend/src/frontend/.eslintignore b/server/zanata-frontend/src/.eslintignore similarity index 73% rename from server/zanata-frontend/src/frontend/.eslintignore rename to server/zanata-frontend/src/.eslintignore index f9baff9c3b..a810ddb292 100644 --- a/server/zanata-frontend/src/frontend/.eslintignore +++ b/server/zanata-frontend/src/.eslintignore @@ -1,2 +1,4 @@ node_modules/** settings.json +*.ts +*.tsx diff --git a/server/zanata-frontend/src/frontend/.eslintrc b/server/zanata-frontend/src/.eslintrc similarity index 54% rename from server/zanata-frontend/src/frontend/.eslintrc rename to server/zanata-frontend/src/.eslintrc index 00c7217394..d09fb06dcd 100644 --- a/server/zanata-frontend/src/frontend/.eslintrc +++ b/server/zanata-frontend/src/.eslintrc @@ -12,3 +12,14 @@ rules: - 0 # Off, can't enforce yet as frontend all has single-quotes - prefer-double # Use double quotes unless the string contains a literal # double-quote character. + # Under standard (non-Babel) ES6 module semantics, you have to import 'react' + # twice in cases like this (no default export): + # import * as React from 'react' + # import { Component } from 'react' + no-duplicate-imports: 0 + max-len: + - 2 // error to enforce compliance, disable when needed + - 80 // line length (characters) + - 2 // count tabs as 2 characters (required, but we don't allow tabs) + // allow JSX to exceed 80 characters (https://twitter.com/timtyrrell/status/689912501165658112) + - { ignorePattern: "\\s*<" } diff --git a/server/zanata-frontend/src/frontend/.flowconfig b/server/zanata-frontend/src/.flowconfig similarity index 100% rename from server/zanata-frontend/src/frontend/.flowconfig rename to server/zanata-frontend/src/.flowconfig diff --git a/server/zanata-frontend/src/.gitignore b/server/zanata-frontend/src/.gitignore new file mode 100644 index 0000000000..712e73d8a2 --- /dev/null +++ b/server/zanata-frontend/src/.gitignore @@ -0,0 +1,2 @@ +build/ +.awcache/ diff --git a/server/zanata-frontend/src/frontend/.jsbeautifyrc b/server/zanata-frontend/src/.jsbeautifyrc similarity index 100% rename from server/zanata-frontend/src/frontend/.jsbeautifyrc rename to server/zanata-frontend/src/.jsbeautifyrc diff --git a/server/zanata-frontend/src/frontend/.jshintrc b/server/zanata-frontend/src/.jshintrc similarity index 100% rename from server/zanata-frontend/src/frontend/.jshintrc rename to server/zanata-frontend/src/.jshintrc diff --git a/server/zanata-frontend/src/frontend/.nvmrc b/server/zanata-frontend/src/.nvmrc similarity index 100% rename from server/zanata-frontend/src/frontend/.nvmrc rename to server/zanata-frontend/src/.nvmrc diff --git a/server/zanata-frontend/src/frontend/.storybook-editor/README.md b/server/zanata-frontend/src/.storybook-editor/README.md similarity index 100% rename from server/zanata-frontend/src/frontend/.storybook-editor/README.md rename to server/zanata-frontend/src/.storybook-editor/README.md diff --git a/server/zanata-frontend/src/frontend/.storybook-editor/__snapshots__/storyshots-editor.test.js.snap b/server/zanata-frontend/src/.storybook-editor/__snapshots__/storyshots-editor.test.js.snap similarity index 89% rename from server/zanata-frontend/src/frontend/.storybook-editor/__snapshots__/storyshots-editor.test.js.snap rename to server/zanata-frontend/src/.storybook-editor/__snapshots__/storyshots-editor.test.js.snap index b0ab79c25d..885cf89162 100644 --- a/server/zanata-frontend/src/frontend/.storybook-editor/__snapshots__/storyshots-editor.test.js.snap +++ b/server/zanata-frontend/src/.storybook-editor/__snapshots__/storyshots-editor.test.js.snap @@ -6,10 +6,10 @@ exports[`Editor Storyshots ActivityFeedItem approved 1`] = ` >

", @@ -58,10 +58,10 @@ exports[`Editor Storyshots ActivityFeedItem approved 1`] = ` title="Copy" > ", @@ -81,10 +81,10 @@ exports[`Editor Storyshots ActivityFeedItem approved 1`] = ` className="u-block small u-sMT-1-2 u-sPB-1-4 u-textMuted u-textSecondary" > ", @@ -115,10 +115,10 @@ exports[`Editor Storyshots ActivityFeedItem comment 1`] = ` >

", @@ -160,10 +160,10 @@ exports[`Editor Storyshots ActivityFeedItem comment 1`] = ` title="Copy" > ", @@ -183,10 +183,10 @@ exports[`Editor Storyshots ActivityFeedItem comment 1`] = ` className="u-block small u-sMT-1-2 u-sPB-1-4 u-textMuted u-textSecondary" > ", @@ -217,10 +217,10 @@ exports[`Editor Storyshots ActivityFeedItem fuzzy 1`] = ` >

", @@ -269,10 +269,10 @@ exports[`Editor Storyshots ActivityFeedItem fuzzy 1`] = ` title="Copy" > ", @@ -292,10 +292,10 @@ exports[`Editor Storyshots ActivityFeedItem fuzzy 1`] = ` className="u-block small u-sMT-1-2 u-sPB-1-4 u-textMuted u-textSecondary" > ", @@ -326,10 +326,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - critical priority 1`] = ` >

", @@ -375,10 +375,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - critical priority 1`] = ` className="CriteriaText" > ", @@ -405,10 +405,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - critical priority 1`] = ` className="well well-sm" > ", @@ -438,10 +438,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - critical priority 1`] = ` title="Copy" > ", @@ -461,10 +461,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - critical priority 1`] = ` className="u-block small u-sMT-1-2 u-sPB-1-4 u-textMuted u-textSecondary" > ", @@ -495,10 +495,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - major priority 1`] = ` >

", @@ -544,10 +544,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - major priority 1`] = ` className="CriteriaText" > ", @@ -574,10 +574,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - major priority 1`] = ` className="well well-sm" > ", @@ -607,10 +607,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - major priority 1`] = ` title="Copy" > ", @@ -630,10 +630,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - major priority 1`] = ` className="u-block small u-sMT-1-2 u-sPB-1-4 u-textMuted u-textSecondary" > ", @@ -664,10 +664,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - minor priority 1`] = ` >

", @@ -713,10 +713,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - minor priority 1`] = ` className="CriteriaText" > ", @@ -743,10 +743,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - minor priority 1`] = ` className="well well-sm" > ", @@ -776,10 +776,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - minor priority 1`] = ` title="Copy" > ", @@ -799,10 +799,10 @@ exports[`Editor Storyshots ActivityFeedItem rejected - minor priority 1`] = ` className="u-block small u-sMT-1-2 u-sPB-1-4 u-textMuted u-textSecondary" > ", @@ -833,10 +833,10 @@ exports[`Editor Storyshots ActivityFeedItem translated 1`] = ` >

", @@ -885,10 +885,10 @@ exports[`Editor Storyshots ActivityFeedItem translated 1`] = ` title="Copy" > ", @@ -908,10 +908,10 @@ exports[`Editor Storyshots ActivityFeedItem translated 1`] = ` className="u-block small u-sMT-1-2 u-sPB-1-4 u-textMuted u-textSecondary" > ", @@ -948,10 +948,10 @@ exports[`Editor Storyshots ActivitySelectList default 1`] = ` type="button" > ", @@ -974,10 +974,10 @@ exports[`Editor Storyshots ActivitySelectList default 1`] = ` type="button" > ", @@ -1000,10 +1000,10 @@ exports[`Editor Storyshots ActivitySelectList default 1`] = ` type="button" > ", @@ -1123,12 +1123,12 @@ exports[`Editor Storyshots Button BUTTON BUILDER 1`] = ` Click Me - Show Info - +

- × - +
@@ -1197,14 +1195,12 @@ exports[`Editor Storyshots Button BUTTON BUILDER 1`] = ` "borderRadius": "2px", "boxShadow": "0px 2px 3px rgba(0, 0, 0, 0.05)", "color": "#444", - "fontFamily": " - -apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", \\"Roboto\\", - \\"Segoe UI\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", sans-serif - ", + "fontFamily": "-apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Roboto\\", \\"Oxygen\\", \\"Ubuntu\\", \\"Cantarell\\", \\"Fira Sans\\", \\"Droid Sans\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", \\"Arial\\", sans-serif", "fontSize": "15px", "fontWeight": 300, "lineHeight": 1.45, - "marginTop": "50px", + "marginBottom": "20px", + "marginTop": "20px", "padding": "20px 40px 40px", } } @@ -1249,21 +1245,18 @@ exports[`Editor Storyshots Button BUTTON BUILDER 1`] = ` } } > -

Use KNOBS tab to adjust the settings, then copy your code from Story Source -

+

+ { onClick() + } @@ -1478,76 +1473,109 @@ exports[`Editor Storyshots Button BUTTON BUILDER 1`] = ` " Component

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ property + propType + required + default + description
- children + + title - other + + - no + + - + - +
- className + + onClick - other + + - no + + - + - +
+ disabled - other + + - no + + - + + { false + } +
- onClick + + children - other + + - no + + - + - +
- title + + className - other + + - no + + - + - +
@@ -1906,12 +1957,12 @@ exports[`Editor Storyshots Button colour styles 1`] = `
- Show Info - +
- × - +
@@ -1980,14 +2029,12 @@ exports[`Editor Storyshots Button colour styles 1`] = ` "borderRadius": "2px", "boxShadow": "0px 2px 3px rgba(0, 0, 0, 0.05)", "color": "#444", - "fontFamily": " - -apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", \\"Roboto\\", - \\"Segoe UI\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", sans-serif - ", + "fontFamily": "-apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Roboto\\", \\"Oxygen\\", \\"Ubuntu\\", \\"Cantarell\\", \\"Fira Sans\\", \\"Droid Sans\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", \\"Arial\\", sans-serif", "fontSize": "15px", "fontWeight": 300, "lineHeight": 1.45, - "marginTop": "50px", + "marginBottom": "20px", + "marginTop": "20px", "padding": "20px 40px 40px", } } @@ -2181,6 +2228,7 @@ exports[`Editor Storyshots Button colour styles 1`] = ` style={Object {}} > + { onClick() + } @@ -2383,6 +2432,7 @@ exports[`Editor Storyshots Button colour styles 1`] = ` style={Object {}} > + { onClick() + } @@ -2585,6 +2636,7 @@ exports[`Editor Storyshots Button colour styles 1`] = ` style={Object {}} > + { onClick() + } @@ -2787,6 +2840,7 @@ exports[`Editor Storyshots Button colour styles 1`] = ` style={Object {}} > + { onClick() + } @@ -2948,76 +3003,109 @@ exports[`Editor Storyshots Button colour styles 1`] = ` " Component - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ property + propType + required + default + description
- children + + title - other + + - no + + - + - +
- className + + onClick - other + + - no + + - + - +
+ disabled - other + + - no + + - + + { false + } +
- onClick + + children - other + + - no + + - + - +
- title + + className - other + + - no + + - + - +
@@ -3344,12 +3455,12 @@ exports[`Editor Storyshots Button plain (disabled) 1`] = ` be fired.
- Show Info - +
- × - +
@@ -3418,14 +3527,12 @@ exports[`Editor Storyshots Button plain (disabled) 1`] = ` "borderRadius": "2px", "boxShadow": "0px 2px 3px rgba(0, 0, 0, 0.05)", "color": "#444", - "fontFamily": " - -apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", \\"Roboto\\", - \\"Segoe UI\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", sans-serif - ", + "fontFamily": "-apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Roboto\\", \\"Oxygen\\", \\"Ubuntu\\", \\"Cantarell\\", \\"Fira Sans\\", \\"Droid Sans\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", \\"Arial\\", sans-serif", "fontSize": "15px", "fontWeight": 300, "lineHeight": 1.45, - "marginTop": "50px", + "marginBottom": "20px", + "marginTop": "20px", "padding": "20px 40px 40px", } } @@ -3549,6 +3656,7 @@ exports[`Editor Storyshots Button plain (disabled) 1`] = ` style={Object {}} > + { onClick() + } @@ -3633,7 +3742,7 @@ exports[`Editor Storyshots Button plain (disabled) 1`] = `
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ property + propType + required + default + description
- children + + title - other + + - no + + - + - +
- className + + onClick - other + + - no + + - + - +
+ disabled - other + + - no + + - + + { false + } +
- onClick + + children - other + + - no + + - + - +
- title + + className - other + + - no + + - + - +
@@ -4128,12 +4293,12 @@ exports[`Editor Storyshots Button plain 1`] = ` Unstyled button. Pretty plain. Should be used with some styles.
- Show Info - +
- × - +
@@ -4202,14 +4365,12 @@ exports[`Editor Storyshots Button plain 1`] = ` "borderRadius": "2px", "boxShadow": "0px 2px 3px rgba(0, 0, 0, 0.05)", "color": "#444", - "fontFamily": " - -apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", \\"Roboto\\", - \\"Segoe UI\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", sans-serif - ", + "fontFamily": "-apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Roboto\\", \\"Oxygen\\", \\"Ubuntu\\", \\"Cantarell\\", \\"Fira Sans\\", \\"Droid Sans\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", \\"Arial\\", sans-serif", "fontSize": "15px", "fontWeight": 300, "lineHeight": 1.45, - "marginTop": "50px", + "marginBottom": "20px", + "marginTop": "20px", "padding": "20px 40px 40px", } } @@ -4333,6 +4494,7 @@ exports[`Editor Storyshots Button plain 1`] = ` style={Object {}} > + { onClick() + } @@ -4425,76 +4588,109 @@ exports[`Editor Storyshots Button plain 1`] = ` " Component - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ property + propType + required + default + description
- children + + title - other + + - no + + - + - +
- className + + onClick - other + + - no + + - + - +
+ disabled - other + + - no + + - + + { false + } +
- onClick + + children - other + + - no + + - + - +
- title + + className - other + + - no + + - + - +
@@ -4817,12 +5036,12 @@ exports[`Editor Storyshots Button rounded and styled 1`] = ` Button Button--small u-rounded Button--primary
- Show Info - +
- × - +
@@ -4891,14 +5108,12 @@ exports[`Editor Storyshots Button rounded and styled 1`] = ` "borderRadius": "2px", "boxShadow": "0px 2px 3px rgba(0, 0, 0, 0.05)", "color": "#444", - "fontFamily": " - -apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", \\"Roboto\\", - \\"Segoe UI\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", sans-serif - ", + "fontFamily": "-apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Roboto\\", \\"Oxygen\\", \\"Ubuntu\\", \\"Cantarell\\", \\"Fira Sans\\", \\"Droid Sans\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", \\"Arial\\", sans-serif", "fontSize": "15px", "fontWeight": 300, "lineHeight": 1.45, - "marginTop": "50px", + "marginBottom": "20px", + "marginTop": "20px", "padding": "20px 40px 40px", } } @@ -5022,6 +5237,7 @@ exports[`Editor Storyshots Button rounded and styled 1`] = ` style={Object {}} > + { onClick() + } @@ -5141,76 +5358,109 @@ exports[`Editor Storyshots Button rounded and styled 1`] = ` " Component - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ property + propType + required + default + description
- children + + title - other + + - no + + - + - +
- className + + onClick - other + + - no + + - + - +
+ disabled - other + + - no + + - + + { false + } +
- onClick + + children - other + + - no + + - + - +
- title + + className - other + + - no + + - + - +
@@ -5444,10 +5717,10 @@ exports[`Editor Storyshots CommentBox default 1`] = ` htmlFor="formControlsTextarea" > ", @@ -5490,10 +5763,10 @@ exports[`Editor Storyshots Concurrent editing notification 1`] = ` role="alert" > ", @@ -5543,10 +5816,10 @@ exports[`Editor Storyshots Concurrent editing transunit items 1`] = ` type="button" > ", @@ -5581,10 +5854,10 @@ exports[`Editor Storyshots DateAndTimeDisplay default 1`] = ` className={undefined} > ", @@ -5615,10 +5888,10 @@ exports[`Editor Storyshots DateAndTimeDisplay styling examples 1`] = ` className={undefined} > ", @@ -5646,10 +5919,10 @@ exports[`Editor Storyshots DateAndTimeDisplay styling examples 1`] = ` className="u-textMicro" > ", @@ -5677,10 +5950,10 @@ exports[`Editor Storyshots DateAndTimeDisplay styling examples 1`] = ` className="u-textMuted u-textMini" > ", @@ -5710,10 +5983,10 @@ exports[`Editor Storyshots DateAndTimeDisplay styling examples 1`] = ` className="u-bgHigher u-sP-1-4" > ", @@ -5743,10 +6016,10 @@ exports[`Editor Storyshots DateAndTimeDisplay styling examples 1`] = ` className="u-bgHigher u-sP-1-2 u-textMuted" > ", @@ -5784,11 +6057,11 @@ exports[`Editor Storyshots GlossarySearchInput empty 1`] = ` onClick={[Function]} > ", @@ -5826,11 +6099,11 @@ exports[`Editor Storyshots GlossarySearchInput with text 1`] = ` onClick={[Function]} > ", @@ -5958,11 +6231,11 @@ exports[`Editor Storyshots GlossaryTerm Without translations 1`] = ` title="Details" > ", @@ -6059,11 +6332,11 @@ exports[`Editor Storyshots GlossaryTerm Without translations 1`] = ` title="Details" > ", @@ -6160,11 +6433,11 @@ exports[`Editor Storyshots GlossaryTerm Without translations 1`] = ` title="Details" > ", @@ -6286,11 +6559,11 @@ exports[`Editor Storyshots GlossaryTerm in a table 1`] = ` title="Details" > ", @@ -6387,11 +6660,11 @@ exports[`Editor Storyshots GlossaryTerm in a table 1`] = ` title="Details" > ", @@ -6488,11 +6761,11 @@ exports[`Editor Storyshots GlossaryTerm in a table 1`] = ` title="Details" > ", @@ -6598,11 +6871,11 @@ exports[`Editor Storyshots GlossaryTerm simple term on its own 1`] = ` title="Details" > ", @@ -6796,10 +7069,10 @@ exports[`Editor Storyshots LanguageSelectList default 1`] = ` type="button" > ", @@ -6822,10 +7095,10 @@ exports[`Editor Storyshots LanguageSelectList default 1`] = ` type="button" > ", @@ -6848,10 +7121,10 @@ exports[`Editor Storyshots LanguageSelectList default 1`] = ` type="button" > ", @@ -7196,10 +7469,10 @@ exports[`Editor Storyshots SelectButton active 1`] = ` type="button" > ", @@ -7225,10 +7498,10 @@ exports[`Editor Storyshots SelectButton default 1`] = ` type="button" > ", @@ -7258,10 +7531,10 @@ exports[`Editor Storyshots SelectButtonList default 1`] = ` type="button" > ", @@ -7284,10 +7557,10 @@ exports[`Editor Storyshots SelectButtonList default 1`] = ` type="button" > ", @@ -7310,10 +7583,10 @@ exports[`Editor Storyshots SelectButtonList default 1`] = ` type="button" > ", @@ -7344,10 +7617,10 @@ exports[`Editor Storyshots SelectButtonList first button active 1`] = ` type="button" > ", @@ -7370,10 +7643,10 @@ exports[`Editor Storyshots SelectButtonList first button active 1`] = ` type="button" > ", @@ -7396,10 +7669,10 @@ exports[`Editor Storyshots SelectButtonList first button active 1`] = ` type="button" > ", diff --git a/server/zanata-frontend/src/frontend/.storybook-editor/addons.js b/server/zanata-frontend/src/.storybook-editor/addons.js similarity index 100% rename from server/zanata-frontend/src/frontend/.storybook-editor/addons.js rename to server/zanata-frontend/src/.storybook-editor/addons.js diff --git a/server/zanata-frontend/src/frontend/.storybook-editor/config.js b/server/zanata-frontend/src/.storybook-editor/config.js similarity index 100% rename from server/zanata-frontend/src/frontend/.storybook-editor/config.js rename to server/zanata-frontend/src/.storybook-editor/config.js diff --git a/server/zanata-frontend/src/frontend/.storybook-editor/storybook.css b/server/zanata-frontend/src/.storybook-editor/storybook.css similarity index 100% rename from server/zanata-frontend/src/frontend/.storybook-editor/storybook.css rename to server/zanata-frontend/src/.storybook-editor/storybook.css diff --git a/server/zanata-frontend/src/frontend/.storybook-editor/storyshots-editor.test.js b/server/zanata-frontend/src/.storybook-editor/storyshots-editor.test.js similarity index 100% rename from server/zanata-frontend/src/frontend/.storybook-editor/storyshots-editor.test.js rename to server/zanata-frontend/src/.storybook-editor/storyshots-editor.test.js diff --git a/server/zanata-frontend/src/frontend/.storybook-editor/storyshots-util.js b/server/zanata-frontend/src/.storybook-editor/storyshots-util.js similarity index 95% rename from server/zanata-frontend/src/frontend/.storybook-editor/storyshots-util.js rename to server/zanata-frontend/src/.storybook-editor/storyshots-util.js index abefdee716..0fb569a083 100644 --- a/server/zanata-frontend/src/frontend/.storybook-editor/storyshots-util.js +++ b/server/zanata-frontend/src/.storybook-editor/storyshots-util.js @@ -1,5 +1,5 @@ /* global jest expect */ -import renderer from 'react-test-renderer' +import * as renderer from 'react-test-renderer' /* * Test function that renders stories into snapshots. diff --git a/server/zanata-frontend/src/frontend/.storybook-editor/webpack.config.js b/server/zanata-frontend/src/.storybook-editor/webpack.config.js similarity index 100% rename from server/zanata-frontend/src/frontend/.storybook-editor/webpack.config.js rename to server/zanata-frontend/src/.storybook-editor/webpack.config.js diff --git a/server/zanata-frontend/src/frontend/.storybook-frontend/README.md b/server/zanata-frontend/src/.storybook-frontend/README.md similarity index 100% rename from server/zanata-frontend/src/frontend/.storybook-frontend/README.md rename to server/zanata-frontend/src/.storybook-frontend/README.md diff --git a/server/zanata-frontend/src/frontend/.storybook-frontend/__snapshots__/storyshots-frontend.test.js.snap b/server/zanata-frontend/src/.storybook-frontend/__snapshots__/storyshots-frontend.test.js.snap similarity index 57% rename from server/zanata-frontend/src/frontend/.storybook-frontend/__snapshots__/storyshots-frontend.test.js.snap rename to server/zanata-frontend/src/.storybook-frontend/__snapshots__/storyshots-frontend.test.js.snap index b5ce80c88e..cf4c8ba418 100644 --- a/server/zanata-frontend/src/frontend/.storybook-frontend/__snapshots__/storyshots-frontend.test.js.snap +++ b/server/zanata-frontend/src/.storybook-frontend/__snapshots__/storyshots-frontend.test.js.snap @@ -1,62 +1,295 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Frontend Storyshots Alert danger 1`] = ` -
- - Holy guacamole! - - Best check yo self -
+ +

+ Danger +

+

+

+ + Holy guacamole! + + Best check yo self +
+

+

+ + bsStyle="danger" + +

+
`; exports[`Frontend Storyshots Alert info 1`] = ` -
- - Holy guacamole! - - Best check yo self -
+ +

+ + Alert +

+
+ Use this for the default alert overlay. In the case where feedback is needed from the user before dismissing the alert, use a + + Notification + + . +
+

+

+ + Holy guacamole! + + Best check yo self +
+

+

+ + bsStyle="info" + +

+
+

+ Props +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ bsClass + + string + + 'alert' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
+ bsStyle + + one of: + + "success" + + , + + "warning" + + , + + "danger" + + , + + "info" + + + 'info' + + Component visual or contextual style variants. +
+ closeLabel + + string + + 'Close alert' + +
+ onDismiss + + function + + + For Closeable alerts pass the + + onDismiss + + function +
+
+

+ Related components +

+ + Notification + +
`; exports[`Frontend Storyshots Alert success 1`] = ` -
- - Holy guacamole! - - Best check yo self -
+ +

+ Success +

+

+

+ + Holy guacamole! + + Best check yo self +
+

+

+ + bsStyle="success" + +

+
`; exports[`Frontend Storyshots Alert warning 1`] = ` -
- - Holy guacamole! - - Best check yo self -
+ +

+ Warning +

+

+

+ + Holy guacamole! + + Best check yo self +
+

+

+ + bsStyle="warning" + +

+
`; exports[`Frontend Storyshots Badge default 1`] = ` -

- Badge - +

+ + Badge +

+
- 23 - -

+ Highlight new or unread items, numbers of members or any other numerical value. +
+

+ Badge + + 23 + +

+
+

+ Props +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ bsClass + + string + + 'badge' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
+ pullRight + + boolean + + false + + Component visual or contextual style variants. +
+
`; exports[`Frontend Storyshots Badge in button 1`] = ` @@ -75,226 +308,53 @@ exports[`Frontend Storyshots Badge in button 1`] = ` `; exports[`Frontend Storyshots Badge in nav item 1`] = ` -
    -
  • +

    + Sidebar navigation +

    + +

    +

    -`; - -exports[`Frontend Storyshots Breadcrumbs default 1`] = ` -
      -
    1. - - Home - -
    2. -
    3. - - Library - -
    4. -
    5. - - Data - -
    6. -
    -`; - -exports[`Frontend Storyshots Button danger 1`] = ` - -`; - -exports[`Frontend Storyshots Button default 1`] = ` - -`; - -exports[`Frontend Storyshots Button group 1`] = ` -
    - - - -
    -`; - -exports[`Frontend Storyshots Button info 1`] = ` - -`; - -exports[`Frontend Storyshots Button nested 1`] = ` -
    - - -
    - - -
    -
    -`; - -exports[`Frontend Storyshots Button primary 1`] = ` - -`; - -exports[`Frontend Storyshots Button sizes 1`] = ` -
    - - - - - - - - -
    -`; - -exports[`Frontend Storyshots Button success 1`] = ` - +

    +
    +

    + Related components +

    +

    + + Sidebar + + , + + Tabs + +

    + `; -exports[`Frontend Storyshots Button toolbar 1`] = ` -
    -
    - - - - -
    -
    - - - -
    +exports[`Frontend Storyshots Breadcrumbs default 1`] = ` + +

    + + Breadcrumbs +

    - + Breadcrumbs are used to indicate the current page's location. All pages 2 levels deep or more should use breadcrumbs in frontend. ie. + + topdir/breadcrumbshere/breadcrumbshere/ +
    -
    -`; - -exports[`Frontend Storyshots Button warning 1`] = ` - -`; - -exports[`Frontend Storyshots Dropdown default 1`] = ` -
    - - -
    + +
    +

    + Props +

    +

    + Breadcrumb component itself doesn't have any specific public properties +

    + `; -exports[`Frontend Storyshots EditableText editing 1`] = ` - -`; - -exports[`Frontend Storyshots EditableText not editing 1`] = ` -
    - Test text -
    +exports[`Frontend Storyshots Button block 1`] = ` + +

    + Button block +

    +

    + +

    +

    + + block=true + +

    +
    `; -exports[`Frontend Storyshots Form checkbox and radios 1`] = ` +exports[`Frontend Storyshots Button danger 1`] = ` -
    - -
    -
    - -
    -
    -
    + Danger button + +

    +

    + + bsStyle="danger" + +

    `; -exports[`Frontend Storyshots Form default 1`] = ` -
    -
    - - -
    -
    - +

    + -

    -
    +

    + +

    +

    + + bsStyle="default" + +

    +
    +

    + Button spacing +

    +

    + Because React doesn't output newlines between elements, buttons on the same line are displayed flush against each other. To preserve the spacing between multiple inline buttons, wrap your button group in ButtonToolbar. +

    +
    +

    + Props +

    + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Name + + Type + + Default + + Description +
    + active + + boolean + + 'false' + +
    + block + + boolean + + 'false' + +
    + bsClass + + string + + 'btn' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
    + bsSize + + one of: + + "lg" + + , + + "large" + + , + + "sm" + + , + + "small" + + , + + "xs" + + , + + "xsmall" + + + + Component size variations +
    + bsStyle + + one of: + + "success" + + , + + "warning" + + , + + "danger" + + , + + "info" + + , + + "default" + + , + + "primary" + + , + + "link" + + + 'default' + + Component visual or contextual style variants. +
    + componentClass + + elementType + + + You can use a custom element type for this component. +
    + disabled + + boolean + + 'false' + +
    + href + + string + + +
    + type + + one of: + + 'button' + + , + + 'reset' + + , + + 'submit' + + + 'button' + + Defines HTML button type attribute +
    + `; -exports[`Frontend Storyshots Form inline label 1`] = ` -
    +exports[`Frontend Storyshots Button group 1`] = ` + +

    + ButtonGroup +

    - -
    - - -
    + className="well well-lg" + > + Group a series of + + Button + + s together on a single line with the + + ButtonGroup + + .
    - -
    +
    + Middle + +
    -
    +
    +

    + Props +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Name + + Type + + Default + + Description +
    + block + + boolean + + 'false' + +
    + bsClass + + string + + 'btn-group' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
    + justified + + boolean + + 'false' + +
    + vertical + + boolean + + 'false' + +
    + `; -exports[`Frontend Storyshots Form validation states 1`] = ` -
    -
    -
    + Info button + +

    +

    + + bsStyle="info" + +

    + +`; + +exports[`Frontend Storyshots Button nested 1`] = ` + +

    + Nested ButtonGroup +

    - - + Use for adding + + Dropdown + + s to + + ButtonGroup + + s
    - - -
    -
    -
    - + `; -exports[`Frontend Storyshots Foundation colours 1`] = ` +exports[`Frontend Storyshots Button primary 1`] = ` -

    - Main colors -

    - - Hover for hexcode - -
    -
    - + Primary button + +

    +

    -
    - + +
    +
    - - #546677 - - -
    -
    - - +
    -
    - +
    +
    - - #A2B3BE - - -
    -
    - - +
    -
    - +
    +
    - - #F5F5F5 - - -
    -
    - - +
    -
    - +
    +

    +

    + + bsSize="lg", "large", "sm", "small", "xs", "xsmall" + +

    +
    +`; + +exports[`Frontend Storyshots Button success 1`] = ` + +

    + Success button +

    +

    +

-

- Status colours -

+ Success button + +

+

+ + bsStyle="success" + +

+ +`; + +exports[`Frontend Storyshots Button toolbar 1`] = ` + +

+ Button toolbar +

- - - #62C876 - - + className="well well-lg" + > + Combine sets of + + ButtonGroup + + s into a + + ButtonToolbar + + for more complex components.
- - - #E9DD00 - - -
-
- - +
-
- - +
-
- - +
-
-`; - -exports[`Frontend Storyshots Foundation grid 1`] = ` -
-
+ 4 + +
- - < - Col xs={12} md={8} - /> - + + +
- - < - Col xs={6} md={4} - /> - +
-
-
- - < - Col xs={6} md={4} - /> - -
-
- - < - Col xs={6} md={4} - /> - -
-
- - < - Col xsHidden md={4} - /> - -
-
-
-
- - < - Col xs={6} xsOffset={6} - /> - -
-
-
-
- - < - Col md={6} mdPush={6} - /> - -
-
- - < - Col md={6} mdPull={6} - /> - -
-
-
-`; - -exports[`Frontend Storyshots Foundation typography 1`] = ` - -

- Font stack -

-

- 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; -


- Headings + Props

-

- Page Header - - With Small Text - -

-

- h1. Bootstrap heading - - Secondary text - -

+ + + + Name + + + Type + + + Default + + + Description + + + + + + + bsClass + + + string + + + 'btn-toolbar' + + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. + + + + +
+`; + +exports[`Frontend Storyshots Button warning 1`] = ` +

- h2. Bootstrap heading - - Secondary text - + Warning button

-

- h3. Bootstrap heading - - Secondary text - -

-

- h4. Bootstrap heading - - Secondary text - -

-
- h5. Bootstrap heading - - Secondary text - -
-
- h6. Bootstrap heading - - Secondary text - -
-
-

- Text styles -

-

- Lead paragraph: vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus. -

- Nullam quis risus eget - - urna mollis ornare - - vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam id dolor id nibh ultricies vehicula. -

-

- - This line of text is meant to be treated as fine print. - -

-

- The following snippet of text is - - rendered as bold text - - . -

-

- The following snippet of text is - - rendered as italicized text - - . + Warning button +

- An abbreviation of the word attribute is - - attr - - . -

-

- Left aligned text. -

-

- Center aligned text. -

-

- Right aligned text. -

-

- Justified text. -

-

- Muted: Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh. -

-

- Primary: Nullam id dolor id nibh ultricies vehicula ut id elit. -

-

- Warning: Etiam porta sem malesuada magna mollis euismod. -

-

- Danger: Donec ullamcorper nulla non metus auctor fringilla. -

-

- Success: Duis mollis, est non commodo luctus, nisi erat porttitor ligula. -

-

- Info: Maecenas sed diam eget risus varius blandit sit amet non magna. + + bsStyle="warning" +

`; -exports[`Frontend Storyshots Icon default 1`] = ` +exports[`Frontend Storyshots Dropdown default 1`] = `

- Size n2 + + Dropdown

- - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - +
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + Dropdown button + + - - - - - - + +
+
+

+ Props +

+

+ The + + Dropdown + + expects at least one component with + + bsRole="toggle" + + and exactly one with + + bsRole="menu" + +

+ - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ bsSize + + one of: + + "lg" + + , + + "large" + + , + + "sm" + + , + + "small" + + , + + "xs" + + , + + "xsmall" + + + + Component size variations +
+ bsClass + + string + + 'btn' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
+ componentClass + + elementType + + ButtonGroup + + You can use a custom element type for this component. +
+ defaultOpen + + boolean + + +
+ disabled + + boolean + + + Whether or not component was disabled. +
+ dropup + + boolean + + + The menu will open above the dropdown button, instead of below it. +
+ id + + required + + + string|number + + An html id attribute, necessary for assistive technologies, such as screen readers. + + Defines HTML button type attribute +
+ noCaret + + boolean + + +
+ onSelect + + function + + + A callback fired when a menu item is selected. +
+            
+              (eventKey: any, event: Object) => any
+            
+          
+
+ onToggle + + function + + + A callback fired when the Dropdown wishes to change visibility. Called with the requested open value, the DOM event, and the source that fired it: +
+            
+              'click','keydown','rootClose', or 'select''
+            
+          
+
+ open + + boolean + + + controlled by: + + onToggle + + , +
+ initial prop: + + defaultOpen + +
+ Whether or not the Dropdown is visible. +
+ pullRight + + boolean + + + Align the menu to the right side of the Dropdown toggle +
+ role + + string + + + If + + 'menuitem' + + , causes the dropdown to behave like a menu item rather than a menu button. +
+ rootCloseEvent + + one of: + + 'click' + + , + + 'mousedown' + + + + Which event when fired outside the component will cause it to be closed +
+ title + + required + + + node + + +
+
+

+ MenuItem +

+

+ This component represents a menu item in a + + Dropdown + + . +

+

+ It supports the basic anchor properties href, target, title. +

+

+ It also supports different properties of the normal Bootstrap MenuItem. +

+
    +
  • + + header + + : To add a header label to sections +
  • +
  • + + divider + + : Adds an horizontal divider between sections +
  • +
  • + + disabled + + : shows the item as disabled, and prevents onSelect from firing +
  • +
  • + + eventKey + + : passed to the callback +
  • +
  • + + onSelect + + : a callback that is called when the user clicks the item. +
  • +
+

+ The callback is called with the following arguments: + + event + + and + + eventKey + +

+
+

+ Props +

+ - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ active + + boolean + + + Highlight the menu item as active. +
+ bsClass + + string + + 'dropdown' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
+ disabled + + boolean + + false + + Disable the menu item, making it unselectable. +
+ divider + + all + + false + + Styles the menu item as a horizontal rule, providing visual separation between groups of menu items. +
+ eventKey + + any + + + Value passed to the + + onSelect + + handler, useful for identifying the selected menu item. +
+ header + + boolean + + false + + Styles the menu item as a header label, useful for describing a group of menu items. +
+ href + + string + + + HTML href attribute corresponding to + + a.href + + . +
+ onClick + + function + + + Callback fired when the menu item is clicked. +
+ onSelect + + function + + + Callback fired when the menu item is selected. +
+            
+              (eventKey: any, event: Object) => any
+            
+          
+
+
+`; + +exports[`Frontend Storyshots EditableText editing 1`] = ` + +

+ + EditableText +

+
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - + +
+

+ Props +

+ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ children + + string + + + String value for this text field +
+ editable + + boolean + + false + + Toggle whether the text field is in editable or not. +
+ editing + + boolean + + false + + Toggle whether the text field is in editing mode or not. +
+ placeholder + + string + + + Field placeholder text +
+ emptyReadOnlyText + + string + + + String to display if it is editable and children is empty and there is not placeholder +
+ title + + string + + + Field tooltip +
+
+`; + +exports[`Frontend Storyshots EditableText not editing 1`] = ` + +

+ + EditableText - disabled +

+
+ Test text +
+
+`; + +exports[`Frontend Storyshots Form default 1`] = ` + +

+ + Forms +

+ +
+
- ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + - - - - - - +
+
- ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + - - - - - - +
- ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + - - - - + +
+

+ Input sizes +

+

+ Use + + bsSize + + on + + FormGroup + + or + + InputGroup + + to change the size of inputs. It also works with add-ons and most other options. +

+
+`; + +exports[`Frontend Storyshots Form validation states 1`] = ` + +

+ + Form validation states +

+
+ Set validationState to one of 'success', 'warning' or 'error' to show validation state. Set + + validationState + + to null (or undefined) to hide validation state. Add + + FormControl.Feedback + + for a feedback icon when validation state is set. +
+
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + + - - - - - - +
- ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + + - - - - - - +
- ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + + - - - - - - +
- ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + + + + @ + + + + - - - - + +

+ Checkboxes and radios +

+
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + - - - - +
+
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + - - - - +
+
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } + - - - - +
+
+`; + +exports[`Frontend Storyshots Foundation colours 1`] = ` + +

+ Main colors +

+ + Hover for hexcode + +
+
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #03A6D7 + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #546677 + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #629BAC + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #A2B3BE + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #BDD4DC + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #F5F5F5 + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #DDDDDD + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #555555 + - - - - - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - +

+ Status colours +

+
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #62C876 + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #E9DD00 + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #FFA800 + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #FF3B3D + - - - +
- - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> + + #4E9FDD + - - - + +`; + +exports[`Frontend Storyshots Foundation grid 1`] = ` + +

+ Grids and flexbox +

+
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - + flexbox + + throughout frontend where it provides a more suitable alternative to the restrictive 12 column grid layout + +
+
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - + + < + Col xs={12} md={8} + /> + +
+
+ + < + Col xs={6} md={4} + /> + +
+
+
+
+
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - +
- ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - + +
+ +
+ +

+ The + + CSS Grid + + will be a good future replacement for the bootstrap columns/flexbox combinations, but currently browser support is below 80%. ( + + CanIUse + + ) +

+ +
+

+ +
+ +
+

+

+ Add more css utility classes so that developers can add minor changes such as padding without working with the style.less file +

+
+
+
+`; + +exports[`Frontend Storyshots Foundation typography 1`] = ` + +

+ Font stack +

+
- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - + 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; + +
+
+

+ Headings +

+

- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - + With Small Text + +

+

+ h1. Bootstrap heading + + Secondary text + +

+

+ h2. Bootstrap heading + + Secondary text + +

+

+ h3. Bootstrap heading + + Secondary text + +

+

+ h4. Bootstrap heading + + Secondary text + +

+
+ h5. Bootstrap heading + + Secondary text + +
+
+ h6. Bootstrap heading + + Secondary text + +
+
+

+ Text styles +

+

- - +

+ Nullam quis risus eget + - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - - - + vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam id dolor id nibh ultricies vehicula. +

+

+ + This line of text is meant to be treated as fine print. + +

+

+ The following snippet of text is + + rendered as bold text + + . +

+

+ The following snippet of text is + + rendered as italicized text + + . +

+

+ An abbreviation of the word attribute is + - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - + . +

+

- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - - +

- - - ", - } - } - style={ - Object { - "fill": "currentColor", - } - } - /> - - - + Center aligned text. +

+

+ Right aligned text. +

+

+ Justified text. +

+

+ Muted: Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh. +

+

+ Primary: Nullam id dolor id nibh ultricies vehicula ut id elit. +

+

+ Warning: Etiam porta sem malesuada magna mollis euismod. +

+

+ Danger: Donec ullamcorper nulla non metus auctor fringilla. +

+

+ Success: Duis mollis, est non commodo luctus, nisi erat porttitor ligula. +

+

+ Info: Maecenas sed diam eget risus varius blandit sit amet non magna. +

+
+`; + +exports[`Frontend Storyshots Icon default 1`] = ` + +

+ + Icons +

+
+ Icons for use throughout frontend. Use + + className + + prop to set size. +
+
+

+ Props +

+ + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ name + + string + + + Icon name +
+ className + + string + + + Icon size +
+
- - + Icon list + +

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + admin +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + all +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + assign +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + attach +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + block +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + chevron-down-double +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + chevron-down +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + chevron-left +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + chevron-right +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + chevron-up-double +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + chevron-up +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - - ", + /> + + + circle +

+

+ + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + clock +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + code +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + comment +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + copy +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + cross-circle +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + cross +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + dashboard +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + document +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + dot +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - -

- Size n1 -

- - - + + + download +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + edit +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + ellipsis +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + export +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + external-link +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - - ", + /> + + + filter +

+

+ + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + folder +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + glossary +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + help +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + history +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + import +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + inbox +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + info +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + keyboard +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + language +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + link +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + location +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + locked +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + logout +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + mail +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + maintain +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + menu +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + minus +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + next +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + notification +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + piestats +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + plus +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + previous +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + project +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + refresh +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + review +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + search +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + servmon +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + settings +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + star-outline +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - - ", + /> + + + star +

+

+ + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + statistics +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + suggestions +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + tick-circle +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + tick +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + tm +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + translate +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - - - - - + + + trash +

+

- ", + + ", + } } - } - style={ - Object { - "fill": "currentColor", + style={ + Object { + "fill": "currentColor", + } } - } - /> - - + /> + + + undo +

+

+ + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + unlocked +

+

+ + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + upload +

+

+ + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + user +

+

+ + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + users +

+

+ + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + version +

+

+ + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + warning +

+

+ + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + zanata +

+ + className="s1" name=name + +
+`; + +exports[`Frontend Storyshots Icon sizes 1`] = ` + +

+ + Icon sizes +

+

+ Size n2 +

", + "__html": "", } } style={ @@ -3951,17 +4545,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -3974,17 +4568,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -3997,17 +4591,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4020,17 +4614,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4043,17 +4637,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4066,17 +4660,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4089,17 +4683,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4112,17 +4706,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4135,17 +4729,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4158,17 +4752,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4181,17 +4775,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4204,17 +4798,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4227,17 +4821,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4250,17 +4844,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4273,17 +4867,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4296,17 +4890,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4319,17 +4913,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4342,17 +4936,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4365,17 +4959,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4388,17 +4982,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4411,17 +5005,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4434,17 +5028,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4457,17 +5051,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4480,17 +5074,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4503,17 +5097,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4526,17 +5120,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4549,17 +5143,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4572,17 +5166,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4594,21 +5188,18 @@ exports[`Frontend Storyshots Icon default 1`] = ` -

- Size 0 -

", + "__html": "", } } style={ @@ -4621,17 +5212,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4644,17 +5235,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4667,17 +5258,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4690,17 +5281,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4713,17 +5304,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4736,17 +5327,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4759,17 +5350,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4782,17 +5373,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4805,17 +5396,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4828,17 +5419,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4851,17 +5442,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4874,17 +5465,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4897,17 +5488,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4920,17 +5511,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4943,17 +5534,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4966,17 +5557,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -4989,17 +5580,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5012,17 +5603,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5035,17 +5626,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5058,17 +5649,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5081,17 +5672,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5104,17 +5695,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5127,17 +5718,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5150,17 +5741,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5173,17 +5764,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5196,17 +5787,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5219,17 +5810,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5242,17 +5833,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5265,17 +5856,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5288,17 +5879,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5311,17 +5902,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5334,17 +5925,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5357,17 +5948,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5380,17 +5971,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5403,17 +5994,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5426,17 +6017,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5449,17 +6040,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5472,17 +6063,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5495,17 +6086,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5518,17 +6109,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5541,17 +6132,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5564,17 +6155,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5586,18 +6177,26 @@ exports[`Frontend Storyshots Icon default 1`] = ` +

+ + className="n2" name=name + +

+

+ Size n1 +

", + "__html": "", } } style={ @@ -5610,17 +6209,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5633,17 +6232,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5656,17 +6255,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5679,17 +6278,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5702,17 +6301,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5725,17 +6324,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5748,17 +6347,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5771,17 +6370,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5794,17 +6393,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5817,17 +6416,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5840,17 +6439,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5863,17 +6462,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5886,17 +6485,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5909,17 +6508,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5932,17 +6531,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5955,17 +6554,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -5978,17 +6577,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6001,17 +6600,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6024,17 +6623,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6047,17 +6646,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6070,17 +6669,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6093,17 +6692,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6116,17 +6715,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6139,17 +6738,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6162,17 +6761,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6185,17 +6784,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6208,17 +6807,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6231,17 +6830,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6253,21 +6852,18 @@ exports[`Frontend Storyshots Icon default 1`] = ` -

- Size 1 -

", + "__html": "", } } style={ @@ -6280,17 +6876,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6303,17 +6899,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6326,17 +6922,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6349,17 +6945,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6372,17 +6968,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6395,17 +6991,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6418,17 +7014,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6441,17 +7037,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6464,17 +7060,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6487,17 +7083,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6510,17 +7106,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6533,17 +7129,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6556,17 +7152,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6579,17 +7175,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6602,17 +7198,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6625,17 +7221,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6648,17 +7244,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6671,17 +7267,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6694,17 +7290,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6717,17 +7313,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6740,17 +7336,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6763,17 +7359,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6786,17 +7382,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6809,17 +7405,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6832,17 +7428,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6855,17 +7451,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6878,17 +7474,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6901,17 +7497,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6924,17 +7520,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6947,17 +7543,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6970,17 +7566,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -6993,17 +7589,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7016,17 +7612,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7039,17 +7635,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7062,17 +7658,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7085,17 +7681,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7108,17 +7704,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7131,17 +7727,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7154,17 +7750,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7177,17 +7773,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7200,17 +7796,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7223,17 +7819,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7245,18 +7841,26 @@ exports[`Frontend Storyshots Icon default 1`] = ` +

+ + className="n1" name=name + +

+

+ Size s0 +

", + "__html": "", } } style={ @@ -7269,17 +7873,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7292,17 +7896,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7315,17 +7919,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7338,17 +7942,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7361,17 +7965,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7384,17 +7988,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7407,17 +8011,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7430,17 +8034,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7453,17 +8057,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7476,17 +8080,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7499,17 +8103,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7522,17 +8126,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7545,17 +8149,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7568,17 +8172,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7591,17 +8195,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7614,17 +8218,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7637,17 +8241,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7660,17 +8264,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7683,17 +8287,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7706,17 +8310,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7729,17 +8333,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7752,17 +8356,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7775,17 +8379,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7798,17 +8402,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7821,17 +8425,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7844,17 +8448,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7867,17 +8471,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7890,17 +8494,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7912,21 +8516,18 @@ exports[`Frontend Storyshots Icon default 1`] = ` -

- Size 2 -

", + "__html": "", } } style={ @@ -7939,17 +8540,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7962,17 +8563,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -7985,17 +8586,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8008,17 +8609,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8031,17 +8632,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8054,17 +8655,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8077,17 +8678,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8100,17 +8701,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8123,17 +8724,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8146,17 +8747,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8169,17 +8770,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8192,17 +8793,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8215,17 +8816,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8238,17 +8839,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8261,17 +8862,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8284,17 +8885,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8307,17 +8908,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8330,17 +8931,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8353,17 +8954,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8376,17 +8977,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8399,17 +9000,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8422,17 +9023,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8445,17 +9046,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8468,17 +9069,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8491,17 +9092,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8514,17 +9115,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8537,17 +9138,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8560,17 +9161,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8583,17 +9184,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8606,17 +9207,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8629,17 +9230,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8652,17 +9253,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8675,17 +9276,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8698,17 +9299,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8721,17 +9322,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8744,17 +9345,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8767,17 +9368,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8790,17 +9391,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8813,17 +9414,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8836,17 +9437,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8859,17 +9460,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8882,17 +9483,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8904,18 +9505,26 @@ exports[`Frontend Storyshots Icon default 1`] = ` +

+ + className="s0" name=name + +

+

+ Size s1 +

", + "__html": "", } } style={ @@ -8928,17 +9537,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8951,17 +9560,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8974,17 +9583,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -8997,17 +9606,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9020,17 +9629,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9043,17 +9652,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9066,17 +9675,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9089,17 +9698,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9112,17 +9721,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9135,17 +9744,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9158,17 +9767,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9181,17 +9790,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9204,17 +9813,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9227,17 +9836,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9250,17 +9859,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9273,17 +9882,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9296,17 +9905,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9319,17 +9928,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9342,17 +9951,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9365,17 +9974,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9388,17 +9997,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9411,17 +10020,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9434,17 +10043,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9457,17 +10066,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9480,17 +10089,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9503,17 +10112,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9526,17 +10135,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9549,17 +10158,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9571,21 +10180,18 @@ exports[`Frontend Storyshots Icon default 1`] = ` -

- Size 3 -

", + "__html": "", } } style={ @@ -9598,17 +10204,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9621,17 +10227,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9644,17 +10250,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9667,17 +10273,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9690,17 +10296,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9713,17 +10319,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9736,17 +10342,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9759,17 +10365,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9782,17 +10388,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9805,17 +10411,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9828,17 +10434,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9851,17 +10457,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9874,17 +10480,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9897,17 +10503,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9920,17 +10526,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9943,17 +10549,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9966,17 +10572,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -9989,17 +10595,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10012,17 +10618,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10035,17 +10641,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10058,17 +10664,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10081,17 +10687,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10104,17 +10710,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10127,17 +10733,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10150,17 +10756,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10173,17 +10779,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10196,17 +10802,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10219,17 +10825,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10242,17 +10848,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10265,17 +10871,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10288,17 +10894,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10311,17 +10917,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10334,17 +10940,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10357,17 +10963,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10380,17 +10986,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10403,17 +11009,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10426,17 +11032,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10449,17 +11055,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10472,17 +11078,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10495,17 +11101,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10518,17 +11124,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10541,17 +11147,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10563,18 +11169,26 @@ exports[`Frontend Storyshots Icon default 1`] = ` +

+ + className="s1" name=name + +

+

+ Size s2 +

", + "__html": "", } } style={ @@ -10587,17 +11201,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10610,17 +11224,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10633,17 +11247,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10656,17 +11270,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10679,17 +11293,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10702,17 +11316,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10725,17 +11339,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10748,17 +11362,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10771,17 +11385,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10794,17 +11408,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10817,17 +11431,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10840,17 +11454,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10863,17 +11477,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10886,17 +11500,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10909,17 +11523,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10932,17 +11546,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10955,17 +11569,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -10978,17 +11592,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11001,17 +11615,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11024,17 +11638,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11047,17 +11661,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11070,17 +11684,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11093,17 +11707,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11116,17 +11730,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11139,17 +11753,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11162,17 +11776,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11185,17 +11799,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11208,17 +11822,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11230,21 +11844,18 @@ exports[`Frontend Storyshots Icon default 1`] = ` -

- Size 4 -

", + "__html": "", } } style={ @@ -11257,17 +11868,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11280,17 +11891,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11303,17 +11914,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11326,17 +11937,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11349,17 +11960,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11372,17 +11983,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11395,17 +12006,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11418,17 +12029,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11441,17 +12052,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11464,17 +12075,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11487,17 +12098,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11510,17 +12121,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11533,17 +12144,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11556,17 +12167,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11579,17 +12190,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11602,17 +12213,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11625,17 +12236,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11648,17 +12259,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11671,17 +12282,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11694,17 +12305,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11717,17 +12328,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11740,17 +12351,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11763,17 +12374,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11786,17 +12397,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11809,17 +12420,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11832,17 +12443,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11855,17 +12466,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11878,17 +12489,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11901,17 +12512,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11924,17 +12535,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11947,17 +12558,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11970,17 +12581,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -11993,17 +12604,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12016,17 +12627,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12039,17 +12650,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12062,17 +12673,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12085,17 +12696,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12108,17 +12719,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12131,17 +12742,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12154,17 +12765,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12177,17 +12788,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12200,17 +12811,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12222,18 +12833,26 @@ exports[`Frontend Storyshots Icon default 1`] = ` +

+ + className="s2" name=name + +

+

+ Size s3 +

", + "__html": "", } } style={ @@ -12246,17 +12865,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12269,17 +12888,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12292,17 +12911,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12315,17 +12934,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12338,17 +12957,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12361,17 +12980,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12384,17 +13003,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12407,17 +13026,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12430,17 +13049,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12453,17 +13072,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12476,17 +13095,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12499,17 +13118,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12522,17 +13141,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12545,17 +13164,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12568,17 +13187,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12591,17 +13210,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12614,17 +13233,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12637,17 +13256,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12660,17 +13279,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12683,17 +13302,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12706,17 +13325,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12729,17 +13348,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12752,17 +13371,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12775,17 +13394,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12798,17 +13417,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12821,17 +13440,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12844,17 +13463,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12867,17 +13486,17 @@ exports[`Frontend Storyshots Icon default 1`] = ` ", + "__html": "", } } style={ @@ -12889,2034 +13508,8042 @@ exports[`Frontend Storyshots Icon default 1`] = ` -
-`; - -exports[`Frontend Storyshots Label in headings 1`] = ` - -

- Label - - New - -

-

- Label + + - New - -

-

- Label - - New + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> -

-

- Label + + + + - New + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> -

-
- Label + + + + - New + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> -
-

- Label + + + + - New + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> -

-
-`; - -exports[`Frontend Storyshots Label validation states 1`] = ` - + + - Default + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + - Primary + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + - Success + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + - Warning + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + - Danger + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + -
-`; - -exports[`Frontend Storyshots Link link page not in frontend app 1`] = ` - - Japanese - -`; - -exports[`Frontend Storyshots Link link within frontend app 1`] = ` - - Languages - -`; - -exports[`Frontend Storyshots List default 1`] = ` -
    -
  • - Item 1 -
  • -
  • - Item 2 -
  • -
  • - ... -
  • -
-`; - -exports[`Frontend Storyshots List with headings 1`] = ` -
-

- Heading 1 -

-

- Some body text -

+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> +
+ - -

- Heading 2 -

-

- Linked item -

-
-
-`; - -exports[`Frontend Storyshots List with links 1`] = ` - -`; - -exports[`Frontend Storyshots LoaderText default 1`] = ` - - - Updating + - -
-", - } + ", } - style={ - Object { - "fill": "#546677", - "height": 64, - "width": 64, - } + } + style={ + Object { + "fill": "currentColor", } - /> - + } + /> + - -`; - -exports[`Frontend Storyshots Modal TMX export - file prep finished 1`] = ` -
-`; - -exports[`Frontend Storyshots Modal TMX export - multiple languages 1`] = ` -
-`; - -exports[`Frontend Storyshots Modal TMX export - one language 1`] = ` -
-`; - -exports[`Frontend Storyshots Modal TMX export - preparing files 1`] = ` -
-`; - -exports[`Frontend Storyshots Modal default 1`] = ` -
-`; - -exports[`Frontend Storyshots Notification Error 1`] = `undefined`; - -exports[`Frontend Storyshots Notification Info 1`] = `undefined`; - -exports[`Frontend Storyshots Notification Warning 1`] = `undefined`; - -exports[`Frontend Storyshots Pagination default 1`] = ` -
    -
  • - - 1 - -
  • -
  • - - 2 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 3 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 4 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 5 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 6 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 7 - -
  • -
  • - - 8 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 9 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 10 - -
  • -
-`; - -exports[`Frontend Storyshots Pagination large 1`] = ` -
    -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 1 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 2 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 3 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 4 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 5 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 6 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 7 - -
  • -
  • - - 8 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 9 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 10 - -
  • -
-`; - -exports[`Frontend Storyshots Pagination small 1`] = ` -
    -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 1 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 2 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 3 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 4 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 5 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 6 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 7 - -
  • -
  • - - 8 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 9 - -
  • -
  • ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - - 10 - -
  • -
-`; - -exports[`Frontend Storyshots Panel danger 1`] = ` -
-
- Panel header -
-
- Panel content -
-
-`; - -exports[`Frontend Storyshots Panel default 1`] = ` -
-
", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Basic panel example -
-
-`; - -exports[`Frontend Storyshots Panel info 1`] = ` -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Panel header -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Panel content -
-
-`; - -exports[`Frontend Storyshots Panel primary 1`] = ` -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Panel header -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Panel content -
-
-`; - -exports[`Frontend Storyshots Panel success 1`] = ` -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + +

+ + className="s3" name=name + +

+

+ Size s4 +

+ - Panel header -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Panel content -
-
-`; - -exports[`Frontend Storyshots Panel warning 1`] = ` -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Panel header -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Panel content -
-
-`; - -exports[`Frontend Storyshots Panel with heading 1`] = ` -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Panel header -
-
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + - Panel content -
-
-`; - -exports[`Frontend Storyshots ProgressBar default 1`] = ` - -
+ ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + -
+ ", + } } - } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + - 60% + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + + + + + ", + } + } + style={ + Object { + "fill": "currentColor", + } + } + /> + + + +

+ + className="s4" name=name + +

+ +`; + +exports[`Frontend Storyshots Label in headings 1`] = ` + +

+ + Labels in headings +

+
+ Use labels to highlight information. +
+

+ Label + + New + +

+

+ Label + + New + +

+

+ Label + + New + +

+

+ Label + + New + +

+
+ Label + + New + +
+

+ Label + + New + +

+
+

+ Props +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ bsClass + + string + + 'label' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
+ bsStyle + + one of: + + "success" + + , + + "warning" + + , + + "danger" + + , + + "info" + + , + + "default" + + , + + "primary" + + , + + "link" + + + 'default' + + Component visual or contextual style variants. +
+
+`; + +exports[`Frontend Storyshots Label validation states 1`] = ` + +

+ + Label validation states +

+ + Default + +

+ + bsStyle="default" + +

+ + Primary + +

+ + bsStyle="primary" + +

+ + Success + +

+ + bsStyle="success" + +

+ + Warning + +

+ + bsStyle="warning" + +

+ + Danger + +

+ + bsStyle="danger" + +

+
+`; + +exports[`Frontend Storyshots Link link page not in frontend app 1`] = ` + +

+ + Link *not* in frontend +

+
+ Use this link colour anywhere outside of frontend. ie. Editor, zanata.org +
+ + Japanese + +
+`; + +exports[`Frontend Storyshots Link link within frontend app 1`] = ` + +

+ + Link in frontend +

+
+ Common link component which generates + + a href + + or in-page navigation link based on useHref. +
+ + Languages + +
+

+ Props +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ id + + string + + + ID attribute +
+ link + + string + + + HTML url or location#hash +
+ useHref + + bool + + false + + Toggle whether to use + + a href + + or in-page navigation +
+ children + + node + + +
+
+`; + +exports[`Frontend Storyshots List group default 1`] = ` + +

+ + List group +

+
+ List groups are a flexible and powerful component for displaying not only simple lists of elements, but complex ones with custom content. +
+
    +
  • + Item 1 +
  • +
  • + Item 2 +
  • +
  • + ... +
  • +
+
+

+ Props +

+

+ ListGroup +

+ + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ bsClass + + string + + 'list-group' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
+ componentClass + + elementType + + + You can use a custom element type for this component. If not specified, it will be treated as + + 'li' + + if every child is a non-actionable + + ListGroupItem> + + , and + + 'div' + + otherwise. +
+

+ ListGroupItem +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ active + + any + + +
+ bsClass + + string + + 'list-group-item' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
+ bsStyle + + one of: + + "success" + + , + + "warning" + + , + + "danger" + + , + + "info" + + , + + "default" + + , + + "primary" + + , + + "link" + + + 'default' + + Component visual or contextual style variants. +
+ disabled + + any + + +
+ header + + node + + +
+ href + + string + + +
+ listItem + + boolean + + false + +
+ onClick + + function + + +
+ type + + string + + +
+
+`; + +exports[`Frontend Storyshots List group with headings 1`] = ` + +

+ + With headings +

+
+ Set the + + header + + prop to create a structured item, with a heading and a body area. +
+
+ +

+ Heading 1 +

+

+ Some body text +

+
+ +

+ Heading 2 +

+

+ Linked item +

+
+
+
+`; + +exports[`Frontend Storyshots List group with links 1`] = ` + +

+ + With links +

+
+ Set the + + href + + or + + onClick + + prop on + + ListGroupItem + + , to create a linked or clickable element. +
+ +
+`; + +exports[`Frontend Storyshots LoaderText default 1`] = ` + +

+ + LoaderText +

+
+ Use this when there is content that needs to be loaded so that the user is given feedback. +
+ + + Updating + + +
+", + } + } + style={ + Object { + "fill": "#546677", + "height": 64, + "width": 64, + } + } + /> + + + + + +`; + +exports[`Frontend Storyshots Modal TMX export - file prep finished 1`] = ` +
+`; + +exports[`Frontend Storyshots Modal TMX export - multiple languages 1`] = ` +
+`; + +exports[`Frontend Storyshots Modal TMX export - one language 1`] = ` +
+`; + +exports[`Frontend Storyshots Modal TMX export - preparing files 1`] = ` +
+`; + +exports[`Frontend Storyshots Modal default 1`] = ` +
+`; + +exports[`Frontend Storyshots Notification Error 1`] = `undefined`; + +exports[`Frontend Storyshots Notification Info 1`] = `undefined`; + +exports[`Frontend Storyshots Notification Warning 1`] = `undefined`; + +exports[`Frontend Storyshots Pagination default 1`] = ` + +

+ + Pagination +

+
+ Multi-page pagination component. Set + + items + + to the number of pages. + + activePage + + prop dictates which page is active. + +
+ +
+

+ More options +

+

+ such as + + first + + , + + last + + , + + previous + + , + + next + + , + + boundaryLinks + + and + + ellipsis + + . +

+
+`; + +exports[`Frontend Storyshots Pagination large 1`] = ` + +

+ + Pagination - large +

+ +
+

+ + bsSize='large' + +

+
+`; + +exports[`Frontend Storyshots Pagination small 1`] = ` + +

+ + Pagination - small +

+ +
+

+ + bsSize='small' + +

+
+`; + +exports[`Frontend Storyshots Panel accordion 1`] = ` + +

+ + Accordion +

+
+ + Accordion + + aliases + + PanelGroup accordion + + +
+
+
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+
+
+`; + +exports[`Frontend Storyshots Panel danger 1`] = ` + +

+ + Panel - danger +

+
+
+ Panel header +
+
+ Panel content +
+
+

+ + bsStyle='danger' + +

+
+`; + +exports[`Frontend Storyshots Panel default 1`] = ` + +

+ + Panel +

+
+ By default, all the + + Panel + + does is apply some basic border and padding to contain some content. You can pass on any additional properties you need, e.g. a custom onClick handler, as it is shown in the example code. They all will apply to the wrapper div element. + +
+
+
+ Basic panel example +
+
+
+`; + +exports[`Frontend Storyshots Panel info 1`] = ` + +

+ + Panel - info +

+
+
+ Panel header +
+
+ Panel content +
+
+

+ + bsStyle='info' + +

+
+`; + +exports[`Frontend Storyshots Panel primary 1`] = ` + +

+ + Panel - primary +

+
+
+ Panel header +
+
+ Panel content +
+
+
+

+ + bsStyle='primary' + +

+
+`; + +exports[`Frontend Storyshots Panel success 1`] = ` + +

+ + Panel - success +

+
+
+ Panel header +
+
+ Panel content +
+
+

+ + bsStyle='success' + +

+
+`; + +exports[`Frontend Storyshots Panel warning 1`] = ` + +

+ + Panel - warning +

+
+
+ Panel header +
+
+ Panel content +
+
+

+ + bsStyle='warning' + +

+
+`; + +exports[`Frontend Storyshots Panel with heading 1`] = ` + +

+ + Panel with header +

+
+ Easily add a heading container to your panel with the + + header + + prop. +
+
+
+ Panel header +
+
+ Panel content +
+
+
+`; + +exports[`Frontend Storyshots ProgressBar default 1`] = ` + +

+ + Progress bar +

+
+ Provide up-to-date feedback on the progress of a workflow or action with simple yet flexible progress bars. +
+

+ Add a + + label + + prop to show a visible percentage. For low percentages, consider adding a min-width to ensure the label's text is fully visible. +

+
+
+ 60% +
+
+

+ Add a + + srOnly + + prop to hide the label visually. +

+
+
+
+
+

+ Props +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Type + + Default + + Description +
+ active + + boolean + + false + +
+ bsClass + + string + + 'progress-bar' + + Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component. +
+ bsStyle + + one of: + + "success" + + , + + "warning" + + , + + "danger" + + , + + "info" + + , + + "default" + + , + + "primary" + + , + + "link" + + + 'default' + + Component visual or contextual style variants. +
+ children + + onlyProgressBar + + +
+ label + + node + + +
+ max + + number + + 100 + +
+ min + + number + + 0 + +
+ now + + number + + +
+ srOnly + + boolean + + false + +
+ striped + + boolean + + false + +
+ +`; + +exports[`Frontend Storyshots ProgressBar stacked 1`] = ` + +

+ + Progress bars - stacked +

+
+ Nest + + ProgressBar + + s to stack them. +
+
+
+
+
+
+ +`; + +exports[`Frontend Storyshots ProgressBar translation states 1`] = ` + +

+ + Progress bars - translation states +

+
+ The boostrap style classes have been used for the translation progress bars and + + bsStyle + + props are needed. +
+ Translated +
+
+
+

+ + bsStyle='success' + +

+ Approved +
+
+
+

+ + bsStyle='success' + +

+ Needs Work +
+
+
+

+ + bsStyle='warning' + +

+ Rejected +
+
+
+

+ + bsStyle='danger' + +

+ +`; + +exports[`Frontend Storyshots RejectionsForm Admin screen 1`] = ` +
+

+ Reject translations settings +

+
+
+ +
+