diff --git a/Annotations/AnnotationGenerator.php b/Annotations/AnnotationGenerator.php index 930af36..007ce3e 100644 --- a/Annotations/AnnotationGenerator.php +++ b/Annotations/AnnotationGenerator.php @@ -31,6 +31,34 @@ class AnnotationGenerator { public const EXAMPLE_CHAR_LIMIT = 3000; + public const GLOBAL_PARAMETER_NAMES = [ + 'idSite', + 'period', + 'date', + 'segment', + 'expanded', + 'idSubtable', + 'flat', + 'filter_pattern', + 'filter_column', + 'filter_pattern_recursive', + 'filter_column_recursive', + 'filter_excludelowpop', + 'filter_excludelowpop_value', + 'filter_sort_column', + 'filter_sort_order', + 'filter_truncate', + 'filter_limit', + 'filter_offset', + 'keep_summary_row', + 'disable_generic_filters', + 'disable_queued_filters', + 'hideColumns', + 'showColumns', + 'label', + 'idGoal', + ]; + /** * @var DocumentationGenerator */ @@ -41,17 +69,37 @@ class AnnotationGenerator */ protected $reportMetadata; + /** + * @var array[] + */ + protected $missingImportantDataWarnings; + public function __construct(DocumentationGenerator $generator) { $this->generator = $generator; + $this->missingImportantDataWarnings = []; } /** - * Use reflection to generate the OpenAPI annotations to be used by swagger-php. + * Generate all the annotations for a plugin's public API endpoints and return them as an array of strings. A string + * for each line to be output or written to file. + * + * @param string $pluginName The name of the plugin. E.g. TagManager + * @param bool $writeToFile Indicate whether the results should be written to file. Default is false so that a dry + * run won't affect the file-system. + * @param bool $useTmpDir Indicate whether the file should be written in Matomo's tmp/ directory. The default is + * false, meaning that it will be written in the OpenApi/Annotations/ directory of the plugin, creating the + * directory if it doesn't already exist. This is useful if we just want a temp file for comparison. like during + * testing. + * + * @return string[]|array[] The collection of all the lines which make up the generated annotations for the public API + * endpoints defined by the plugin. + * @throws \Piwik\Exception\PluginDeactivatedException If the plugin is not activated. It should be loaded. + * @throws \Throwable */ public function generatePluginApiAnnotations(string $pluginName, bool $writeToFile = false, bool $useTmpDir = false): array { - BaseValidator::check('plugin', $pluginName, [ new NotEmpty() ]); + BaseValidator::check('plugin', $pluginName, [new NotEmpty()]); Manager::getInstance()->checkIsPluginActivated($pluginName); $currentPluginDir = Manager::getInstance()::getPluginDirectory('OpenApiDocs'); @@ -93,12 +141,41 @@ public function generatePluginApiAnnotations(string $pluginName, bool $writeToFi $this->writeAnnotationsToFile($annotations, $pluginAnnotationPath, $pluginName); } - return $annotations; + if (count($this->missingImportantDataWarnings) === 0) { + return $annotations; + } + + $lines = []; + foreach ($this->missingImportantDataWarnings as $methodName => $warnings) { + if (empty($warnings)) { + continue; + } + + $lines[] = $methodName . ' has the following warnings:'; + foreach ($warnings as $paramName => $warningLines) { + if (empty($warningLines)) { + continue; + } + + $lines[] = '- ' . $paramName . ':'; + $lines[] = " - " . implode("\n - ", $warningLines); + } + } + + return $lines; } - protected function writeAnnotationsToFile(array $annotations, string $filePath, string $pluginName): void + /** + * Write the collection of annotation lines to file, overwriting the file if it already exists. + * + * @param array[] $annotations Collection of generated annotations. It's an array of arrays containing the lines + * which make up all the annotations which need to be written to file. + * @param string $pluginName Name of the plugin. E.g. TagManager + * + * @return string The full string content of the generated annotations file. + */ + public function getContentForGeneratedAnnotationsFile(array $annotations, string $pluginName): string { - $output = ''; $lines = [ 'getContentForGeneratedAnnotationsFile($annotations, $pluginName)); } + /** + * Build the full array of lines for an OA operation, like OA\Get or OA\Post. This pulls data from various sources, + * including making API calls to get example responses. + * + * @param array $rules An array of configs determining which responses to include by default. + * @param string $pluginName Name of the plugin. E.g. TagManager. + * @param \ReflectionMethod $reflectionMethod The reflective representation of the method to provide metadata. + * + * @return array + * @throws \Throwable + */ protected function buildAnnotationForMethod(array $rules, string $pluginName, \ReflectionMethod $reflectionMethod): array { $existing = $reflectionMethod->getDocComment(); @@ -151,38 +256,56 @@ protected function buildAnnotationForMethod(array $rules, string $pluginName, \R $isPost = !empty($rules['plugins'][$pluginName]['methodsRequiringPost']) && in_array($methodName, $rules['plugins'][$pluginName]['methodsRequiringPost']); - return $this->compileOperationLines($path, $opId, $pluginName, $methodName, $params, $responses, $isPost); + return $this->compileOperationLines($path, $opId, $pluginName, $params, $responses, $isPost); } - protected function getParamInfoFromDocBlock(string $docBlock): array + /** + * Try to extract the list of parameters and key information about them from the method's doc block string. + * + * @param string $docBlock The comment block from a method, which hopefully contains the param annotations. + * + * @return array Of each param provided in the comment block and key information about them like the type and + * description, if available. The array can be empty if there are no param annotations present. E.g. + * ['idSite' => ['type' => 'integer', 'description' => 'Site ID'], 'date' => ['type' => 'string', 'description' => '']] + */ + public function getParamInfoFromDocBlock(string $docBlock): array { - $lexer = new Lexer(); + $lexer = new Lexer(); $tokens = $lexer->tokenize($docBlock); $expressionParser = new ConstExprParser(); $parser = new PhpDocParser(new TypeParser($expressionParser), $expressionParser); - $node = $parser->parse(new TokenIterator($tokens)); + $node = $parser->parse(new TokenIterator($tokens)); $params = []; foreach ($node->getParamTagValues() as $param) { $name = ltrim($param->parameterName, '$'); $params[$name] = [ - 'type' => (string) $param->type, + 'type' => (string)$param->type, // Normalise the description. E.g. remove linebreaks and indentation - 'description' => trim(preg_replace(['/^\h+/m', '/\R+/u',], ['', ' '], $param->description)), - 'byRef' => $param->isReference, + 'description' => trim(preg_replace(['/^\h+/m', '/\R+/u',], ['', ' '], $param->description)), + 'byRef' => $param->isReference, 'variadic' => $param->isVariadic, ]; } return $params; } - protected function getResponseInfoFromDocBlock(string $docBlock): array + /** + * Try to extract the response-type of a method from the doc block string. + * + * @param string $docBlock The comment block from a method, which hopefully contains the return annotation. + * + * @return array The collection of key information about the method's return type if any is found. + * E.g. ['type' => 'integer', 'description' => 'The ID of the newly created report.'] or ['type' => null] if no + * return annotation is present. + */ + public function getResponseInfoFromDocBlock(string $docBlock): array { - $lexer = new Lexer(); + $lexer = new Lexer(); $tokens = $lexer->tokenize($docBlock); $expressionParser = new ConstExprParser(); $parser = new PhpDocParser(new TypeParser($expressionParser), $expressionParser); - $node = $parser->parse(new TokenIterator($tokens)); + $node = $parser->parse(new TokenIterator($tokens)); $responseInfo = ['type' => null]; $returnTags = $node->getReturnTagValues(); @@ -204,14 +327,53 @@ protected function getResponseInfoFromDocBlock(string $docBlock): array return $responseInfo; } - protected function buildVirtualPath(string $virtualPathTemplate, string $plugin, string $method): string + /** + * This is a helper method for building the path used for an operation annotation. It takes a path template, like + * the one from the config array and populates it with the plugin name and API method name. + * + * @param string $virtualPathTemplate The template of what the path should be. + * E.g. /index.php?module=API&method={plugin}.{method} + * @param string $plugin The name of the plugin. E.g. TagManager + * @param string $method The name of the API method. E.g. getCustomReport + * + * @return string The finalised path to be used in an operation annotation. + * E.g. /index.php?module=API&method=CustomReports.getConfiguredReport + */ + public function buildVirtualPath(string $virtualPathTemplate, string $plugin, string $method): string { return str_replace(['{plugin}', '{method}'], [$plugin, $method], $virtualPathTemplate); } - protected function buildParameterAnnotationData(string $paramName, array $paramMetadata, array $paramDocInfo): array + /** + * Build the key data for the specified parameter. This should be all the data necessary to create an OA\Parameter + * annotation object. + * + * @param string $methodName The name of the method. E.g. getAlert + * @param string $paramName The name of the parameter. E.g. idSite or period + * @param array $paramMetadata The collection of metadata from the old DocumentationGenerator class. Things like + * whether the parameter is typed, is required, or has a default value. + * @param array $paramDocInfo The collection of parameter information built from the method doc block. This is + * especially useful when the metadata wasn't able to determine the type. We can check the param annotation for the + * type and description. + * + * @return array The array of key information about the parameter like the type (types if more than one is hinted), + * the name, whether it's required, default value, and example. Since there may be more than one type from the doc + * block, the type is specified as a 'types' array even if there's only one type. E.g. + * [ + * 'name' => 'idSite', + * 'types' => ['integer', 'string'], + * 'description' => 'The ID of the site.', + * 'required' => 'true', // It's a string here, but gets converted to boolean in the annotation. + * 'default' => '\Piwik\API\NoDefaultValue', // This class name indicates no default value since falsy values might be valid. + * 'example' => 1, + * ] + */ + public function buildParameterAnnotationData(string $methodName, string $paramName, array $paramMetadata, array $paramDocInfo): array { $docType = strtolower(trim($paramDocInfo['type'] ?? '')); + if (empty($docType)) { + $this->addMissingImportantDataWarning($methodName, $paramName, 'Type is not specified in comment block.'); + } $metaType = strtolower(trim($paramMetadata['type'] ?? $docType)); $type = $metaType === 'string' && $docType !== 'string' ? $docType : $metaType; // If the signature type is array, but the type hinting provides more, use that instead @@ -233,6 +395,9 @@ protected function buildParameterAnnotationData(string $paramName, array $paramM $isRequired = !key_exists('default', $paramMetadata) || $paramMetadata['default'] instanceof NoDefaultValue; $description = $paramDocInfo['description'] ?? ''; + if (empty($description)) { + $this->addMissingImportantDataWarning($methodName, $paramName, 'Description is not specified in comment block.'); + } $example = ''; // Check the description for the example value if (preg_match('/\[@example\s*=\s*([^\n]+)\]/', $description, $m)) { @@ -246,6 +411,10 @@ protected function buildParameterAnnotationData(string $paramName, array $paramM $example = trim($example, '"'); } + // Clean up the descriptions a little more like removing linebreaks and escaping double-quotes + $description = str_replace("\n", ' ', $description); + $description = str_replace('"', '""', $description); + return [ 'name' => $paramName, 'types' => $typesMap, @@ -256,6 +425,60 @@ protected function buildParameterAnnotationData(string $paramName, array $paramM ]; } + /** + * Add an entry to the map of warnings about missing important information, like type and description of parameters + * and returns. + * + * @param string $methodName Name of the method to more easily identify where in the code needs adjustment. + * @param string $paramName Name of the parameter or "return" for the response. E.g. idSite, period, return, ... + * @param string $message Message indicating what is missing. E.g. "Type is not specified in comment block." + * + * @return void + */ + protected function addMissingImportantDataWarning(string $methodName, string $paramName, string $message): void + { + // Make sure that the inner arrays have been initialised and then add the message to the warning map + $this->missingImportantDataWarnings[$methodName] = $this->missingImportantDataWarnings[$methodName] ?? []; + $this->missingImportantDataWarnings[$methodName][$paramName] = $this->missingImportantDataWarnings[$methodName][$paramName] ?? []; + $this->missingImportantDataWarnings[$methodName][$paramName][] = $message; + } + + /** + * Remove a warning from the collection. This is useful when it's determined after the fact that a parameter has + * a global component which can be used, like idSite or period. + * + * @param string $methodName Name of the method. + * @param string $paramName Name of the parameter or "return" for the response. E.g. idSite, period, return, ... + * + * @return void + */ + protected function removeMissingImportantDataWarning(string $methodName, string $paramName): void + { + if (empty($this->missingImportantDataWarnings[$methodName][$paramName])) { + return; + } + + // If it's the only param in the collection for the method, remove the method + if (count($this->missingImportantDataWarnings[$methodName]) === 1) { + unset($this->missingImportantDataWarnings[$methodName]); + return; + } + + unset($this->missingImportantDataWarnings[$methodName][$paramName]); + } + + /** + * Build the collection of parameters and key information about them for the specified method. + * + * @param array $rules An array of configs determining which responses to include by default. + * @param string $plugin Name of the plugin. E.g. TagManager. + * @param string $method The name of the method being annotated. + * @param \ReflectionMethod $reflectionMethod The reflective representation of the method to provide metadata. + * + * @return array List of each method parameter and key data points like the data type, whether it's required, + * default value, and example value. + * @see self::buildParameterAnnotationData() where the parameter data is built. + */ protected function determineParameters(array $rules, string $plugin, string $method, \ReflectionMethod $reflectionMethod): array { $refs = []; @@ -269,7 +492,11 @@ protected function determineParameters(array $rules, string $plugin, string $met } $paramsMetadata = Proxy::getInstance()->getParametersListWithTypes(Request::getClassNameAPI($plugin), $method); - $paramsInfo = $this->getParamInfoFromDocBlock($reflectionMethod->getDocComment()); + $paramsInfo = []; + $docBlock = $reflectionMethod->getDocComment(); + if (!empty($docBlock)) { + $paramsInfo = $this->getParamInfoFromDocBlock($docBlock); + } $customParams = []; foreach ($paramsMetadata as $name => $paramMetadata) { @@ -280,7 +507,16 @@ protected function determineParameters(array $rules, string $plugin, string $met continue; } - $customParams[] = $this->buildParameterAnnotationData($name, $paramMetadata, $paramInfo); + // If the parameter doesn't have a description and matches a global, use a reference to the global instead. + $customParamData = $this->buildParameterAnnotationData($method, $name, $paramMetadata, $paramInfo); + if (empty($customParamData['description']) && in_array($name, self::GLOBAL_PARAMETER_NAMES)) { + $globalParamSuffix = $customParamData['required'] === 'true' ? 'Required' : 'Optional'; + $refs[] = '#/components/parameters/' . $name . $globalParamSuffix; + $this->removeMissingImportantDataWarning($method, $name); + continue; + } + + $customParams[] = $customParamData; } return [ @@ -296,6 +532,7 @@ protected function determineParameters(array $rules, string $plugin, string $met * @link https://spec.openapis.org/oas/v3.1.1.html#data-types * * @param string $type The PHP type from the method signature or doc-block + * * @return string The normalised Data Type to be used in the swagger-php annotation */ public function getOpenApiTypeFromPhpType(string $type): string @@ -331,6 +568,24 @@ public function getOpenApiTypeFromPhpType(string $type): string return $type; } + /** + * Try to build example URLs for a specific API method. This uses the old DocumentationGenerator to build the same + * example URLs which have been available on the API documentation page for a long time. E.g. XML, JSON, and TSV. + * Unlike the old documentation, this only includes URLs if a valid response was received from the demo server or + * local Matomo instance. For example, some endpoints respond that the data structure is not TSV compatible and the + * old documentation would still include the link. This shows 'TSV (N/A)' in those instances. + * + * @param string $pluginName The name of the plugin. E.g. TagManager. + * @param string $methodName The name of the plugin specific API method. E.g. getCustomReport. + * @param array[] $paramsData The collection of parameter data compiled using reflection and metadata. This includes + * types, default values, and examples. It can be used to build URLs using required parameters which aren't globals, + * like idSite and period which have established example values. + * + * @return array The example URLs with only the required query parameters and only if a valid example responses were + * received when the URL was queried. Empty string if no URL could be determined or no valid response was received. + * E.g. ['xml => 'https://demo...&format=xml', 'json' => 'https://demo...&format=JSON', 'tsv' => 'https://demo...&format=Tsv'] + * @throws \Throwable + */ protected function getApplicableDemoExampleUrls(string $pluginName, string $methodName, array $paramsData): array { // Get the example URLs for the success responses @@ -340,6 +595,14 @@ protected function getApplicableDemoExampleUrls(string $pluginName, string $meth 'date' => 'today', ]; + // Don't build example URLs for anything that isn't the R in CRUD. E.g. No create, update, or delete. + $notAllowedExampleUrlOperations = ['create', 'add', 'save', 'set', 'update', 'delete', 'remove', 'copy', 'duplicate']; + foreach ($notAllowedExampleUrlOperations as $operation) { + if (stripos($methodName, $operation) === 0) { + return []; + } + } + $parametersToReplace = []; if (!empty($paramsData['custom'])) { foreach ($paramsData['custom'] as $customParam) { @@ -393,6 +656,17 @@ protected function getApplicableDemoExampleUrls(string $pluginName, string $meth ]; } + /** + * Query demo.matomo.cloud for report metadata which can later be used to help determine good example URLs for + * specific API endpoints. This method is only used when the example URL can't be determined using the default + * method. This only works for endpoints associated with reports and have metadata provided by the containing + * plugin. The response is cached as a property so the request is only made once regardless of how many times this + * method is called. The exception is if a valid response wasn't received. In that case, it will keep making the + * request until a non-empty response is received. + * + * @return array|array[] The decoded JSON array of all the report metadata from the demo server. + * @throws \Exception + */ protected function getDemoReportMetadata(): array { if (is_array($this->reportMetadata) && count($this->reportMetadata)) { @@ -400,20 +674,25 @@ protected function getDemoReportMetadata(): array } $url = 'https://demo.matomo.cloud/index.php?module=API&method=API.getReportMetadata&format=JSON&idSite=1&hideMetricsDoc=0&showSubtableReports=0&filter_limit=-1&period=day'; - $response = Http::sendHttpRequestBy( - Http::getTransportMethod(), - $url, - $timeout = 10, - $userAgent = null, - $destinationPath = null, - $file = null, - $followDepth = 0, - $acceptLanguage = false, - $acceptInvalidSslCertificate = true, - $byteRange = false, - $getExtendedInfo = true, - $httpMethod = 'GET' - ); + try { + $response = Http::sendHttpRequestBy( + Http::getTransportMethod(), + $url, + $timeout = 30, // We can use a somewhat longer timeout for this request since it's cached afterward. + $userAgent = null, + $destinationPath = null, + $file = null, + $followDepth = 0, + $acceptLanguage = false, + $acceptInvalidSslCertificate = true, + $byteRange = false, + $getExtendedInfo = true, + $httpMethod = 'GET' + ); + } catch (\Exception $e) { + // Add a little bit more context for troubleshooting the failed request + throw new \Exception('Error getting report metadata from URL: ' . $url . PHP_EOL . $e, 0, $e); + } if (empty($response['data']) || ($response['status'] ?? 1) !== 200 || strpos($response['data'], 'Error: ') === 0) { return []; @@ -424,6 +703,20 @@ protected function getDemoReportMetadata(): array return $this->reportMetadata; } + /** + * Take the example URL and query the endpoint for an example response, hiding subtables. If a response isn't + * received from demo.matomo.cloud, it can try using a temporary token to make the request against the current + * instance of Matomo. + * + * @param string $url The full example URL. E.g. + * https://demo.matomo.cloud/?module=API&method=CustomReports.getConfiguredReports&idSite=1&format=xml&token_auth=anonymous + * @param bool $useLocalToken A boolean indicating whether to get a temporary token and try the request against the + * currently running Matomo instance. + * + * @return string The response received from the API endpoint if no error was received or the response wasn't empty. + * An empty string is returned by default. + * @throws \Throwable + */ protected function getExampleIfAvailable(string $url, bool $useLocalToken = false): string { // If the flag to use a temp token is set, get a token and update the request URL @@ -449,7 +742,8 @@ protected function getExampleIfAvailable(string $url, bool $useLocalToken = fals $httpMethod = 'GET' ); } catch (\Throwable $e) { - throw $e; + // Add a little bit more context for troubleshooting the failed request + throw new \Exception('Error getting example from URL: ' . $url . PHP_EOL . $e, 0, $e); } // If the example didn't load or resulted in an error, simply return an empty string @@ -460,11 +754,13 @@ protected function getExampleIfAvailable(string $url, bool $useLocalToken = fals || stripos($response['data'], '"result":"error"') !== false || stripos($response['data'], '') !== false || trim($response['data']) === '[]' + || (stripos($url, 'format=tsv') !== false && trim($response['data']) === 'No data available') ) { return ''; } $body = $response['data']; + // Convert the XML responses into a JSON object and then encode it into a string. This is helpful for building schemas. if (stripos($url, 'format=xml') !== false) { $body = json_encode($this->convertExampleXmlToObject($body)); } @@ -472,6 +768,19 @@ protected function getExampleIfAvailable(string $url, bool $useLocalToken = fals return $body; } + /** + * Try to build an example URL for a specific API method using report metadata. This queries the demo server for + * report metadata to get examples of existing reports which can be used as example URLS. If no metadata matches the + * provided plugin and method, an empty string is returned. Likewise, when no valid response is received for the + * URL. NOTE: This should only be used if the old DocumentationGenerator didn't provide example URLs. + * + * @param string $pluginName The name of the plugin. E.g. TagManager. + * @param string $methodName The name of the plugin specific API method. E.g. getCustomReport. + * + * @return string The example URL with only the required query parameters and only if a valid example response was + * received when the URL was queried. Empty string if no URL could be determined or no valid response was received. + * @throws \Throwable + */ protected function getReportExampleUrlFromMetadata(string $pluginName, string $methodName): string { $metadataArray = $this->getDemoReportMetadata(); @@ -486,7 +795,7 @@ protected function getReportExampleUrlFromMetadata(string $pluginName, string $m // Keep trying until we find a good match if ($metadata['module'] === $pluginName && $metadata['action'] === $methodName) { - if (empty($metadata) || empty($metadata['imageGraphUrl'])) { + if (empty($metadata['imageGraphUrl'])) { continue; } @@ -512,7 +821,19 @@ protected function getReportExampleUrlFromMetadata(string $pluginName, string $m return ''; } - protected function convertExampleXmlToObject(string $xml): array + /** + * Take an XML string, deserialise it, and convert it into a JSON object structured correctly for an XML schema + * example. E.g. Value1Value2 to ["row" => ["Value1","Value2"]] or + * Value1Value2 to + * ["row" => [{"child":"Value1"},{"child":"Value2"}]] + * + * @param string $xml The XML string of an example response for an API endpoint. + * + * @return array The array representation of the JSON object example structured correctly for an XML schema. E.g. + * ["row" => [{"child":"Value1"},{"child":"Value2"}]] + * @throws \Exception + */ + public function convertExampleXmlToObject(string $xml): array { $root = new \SimpleXMLElement($xml); @@ -536,18 +857,35 @@ protected function convertExampleXmlToObject(string $xml): array return [$result]; } - // Return the object that goes into example - return $result; // e.g., [ "row" => [ {...}, {...} ] ] + return $result; } - + /** + * Build the array of potential responses for the API method. E.g. a response for 200, 400, 401, etc. + * + * @param array $rules An array of configs determining which responses to include by default. + * @param string $plugin Name of the plugin. E.g. TagManager. + * @param string $method The name of the method being annotated. + * @param \ReflectionMethod $reflectionMethod The reflective representation of the method to provide metadata. + * @param array $paramsData An array of already built method parameter data. This is used while building example + * URLs because the generator doesn't know what value to use for non-global parameters like idSite and period. We + * check the paramsData to see if an example value was provided for all the required parameters so that an example + * can be queried. + * + * @return array A collection of annotation lines for each of the expected potential responses for the method. + * @throws \Throwable + */ protected function determineResponses(array $rules, string $plugin, string $method, \ReflectionMethod $reflectionMethod, array $paramsData): array { $responses = []; // Try to determine the success response using the return type and/or doc-block return type $returnType = $reflectionMethod->getReturnType(); - $responseInfo = $this->getResponseInfoFromDocBlock($reflectionMethod->getDocComment()); + $responseInfo = []; + $docBlock = $reflectionMethod->getDocComment(); + if (!empty($docBlock)) { + $responseInfo = $this->getResponseInfoFromDocBlock($docBlock); + } if (!empty($returnType) && $returnType->isBuiltin()) { $responseInfo['type'] = $this->getOpenApiTypeFromPhpType(strval($returnType)); } @@ -592,6 +930,8 @@ protected function determineResponses(array $rules, string $plugin, string $meth if (!empty($responseInfo['description'])) { $successArray['description'] = $responseInfo['description']; + } elseif (empty($successArray['ref'])) { + $this->addMissingImportantDataWarning($method, 'return', 'Description is not specified in comment block.'); } $responseSchema = !empty($responseInfo['type']) ? $this->buildSchemaObjectArray($responseInfo['type']) : []; @@ -605,11 +945,7 @@ protected function determineResponses(array $rules, string $plugin, string $meth if ($type === 'tsv') { $url .= '&convertToUnicode=0'; } - try { - $exampleValue = $this->getExampleIfAvailable($url); - } catch (\Throwable $e) { - throw new \Exception('Error getting example from URL: ' . $url . PHP_EOL . $e, 0, $e); - } + $exampleValue = $this->getExampleIfAvailable($url); // If the example lookup failed, try making the same request locally $isLocalExample = false; if (empty($exampleValue)) { @@ -667,7 +1003,7 @@ protected function determineResponses(array $rules, string $plugin, string $meth $successArray['description'] = ''; } } else { - // Make sure the schema is included in there are no examples + // Make sure the schema is included if there are no examples $successArray['schema'] = $responseSchema; } @@ -681,6 +1017,10 @@ protected function determineResponses(array $rules, string $plugin, string $meth // Append the links to the description with a prefix linebreak. If there's no description, skip the break $successArray['description'] .= (!empty($successArray['description']) && !empty($descriptionLinks) ? '
' : '') . $descriptionLinks; + if (empty($successArray['ref']) && empty($descriptionLinks) && empty($successArray['schema'])) { + $this->addMissingImportantDataWarning($method, 'return', 'Type could not be determined via comment block or example.'); + } + $responses[] = $successArray; if (!empty($rules['defaultErrorResponseRefs'])) { @@ -692,7 +1032,20 @@ protected function determineResponses(array $rules, string $plugin, string $meth return $responses; } - protected function cutExampleCloseToCharLimit(string $exampleValue, string $type): string + /** + * Take a string example and make sure that it is close to the max char limit. There's a little wiggle room due to + * wrapping elements and whitespace characters, but it should be within 100 characters of the limit. To do this, we + * deserialise the example based on type and iterate over the first-level properties and append them to a new + * example string. If a single property exceeds the limit or will if added to the newly built string, we skip it. If + * none of the base properties are small enough, we simply return an empty string. + * + * @param string $exampleValue The example response received from the demo or other server. + * @param string $type The type of the parameter. E.g. xml, json, or tsv + * + * @return string A new example string within a reasonable variation from the limit. If no row of the example fits + * within the limit, the result is an empty string. + */ + public function cutExampleCloseToCharLimit(string $exampleValue, string $type): string { if (empty($exampleValue)) { return ''; @@ -725,7 +1078,7 @@ protected function cutExampleCloseToCharLimit(string $exampleValue, string $type $rows = $decodedRows['row']; } $newRows = []; - foreach ($rows as $row) { + foreach ($rows as $key => $row) { // Don't add the row if it would exceed the limit if ( strlen(json_encode($row)) > self::EXAMPLE_CHAR_LIMIT @@ -734,6 +1087,13 @@ protected function cutExampleCloseToCharLimit(string $exampleValue, string $type continue; } + // If it's a named element, add it back by name + if (is_string($key)) { + $newRows[$key] = $row; + continue; + } + + // Since it wasn't a named row, it must be an array can simply be added back $newRows[] = $row; } @@ -741,7 +1101,7 @@ protected function cutExampleCloseToCharLimit(string $exampleValue, string $type return ''; } - if (!empty($decodedRows['row'])) { + if (!empty($decodedRows['row']) && is_array($decodedRows['row'])) { $decodedRows['row'] = $newRows; } else { $decodedRows = $newRows; @@ -750,7 +1110,14 @@ protected function cutExampleCloseToCharLimit(string $exampleValue, string $type return json_encode($decodedRows); } - protected function buildSchemaAnnotationFromJsonExample(array $jsonArrayObject): array + /** + * Take the deserialised structure of an JSON object and build the lines of an OA\Schema annotation object for it. + * + * @param array $jsonArrayObject Nested array of properties of the JSON object. + * + * @return array Collection of potentially nested arrays representing an OA\Property annotation object. + */ + public function buildSchemaAnnotationFromJsonExample(array $jsonArrayObject): array { // Since the schema is pretty much the same as the property, let's just build a property and replace the key $propertyLines = $this->buildPropertyAnnotationFromJsonExample('', $jsonArrayObject); @@ -758,7 +1125,15 @@ protected function buildSchemaAnnotationFromJsonExample(array $jsonArrayObject): return ['@OA\Schema' => $propertyLines['@OA\Property']]; } - protected function buildPropertyAnnotationFromJsonExample(string $propName, array $values): array + /** + * Take the deserialised structure of an JSON object and build the lines of an OA\Property annotation object for it. + * + * @param string $propName Name of the JSON property. + * @param array $values Nested array of properties of the JSON property. + * + * @return array Collection of potentially nested arrays representing an OA\Property annotation object. + */ + public function buildPropertyAnnotationFromJsonExample(string $propName, array $values): array { $type = 'object'; // If the first key isn't a string, it's an array @@ -793,7 +1168,7 @@ protected function buildPropertyAnnotationFromJsonExample(string $propName, arra foreach ($values as $key => $value) { // If it's not an array, add a simple property string and skip to the next child if (!is_array($value)) { - $typesString = '"string", "number", "integer", "boolean", "array", "object", "null"'; + $typesString = '{"string", "number", "integer", "boolean", "array", "object", "null"}'; if (is_string($value)) { $typesString = '"string"'; } elseif (is_int($value)) { @@ -801,7 +1176,7 @@ protected function buildPropertyAnnotationFromJsonExample(string $propName, arra } elseif (is_bool($value)) { $typesString = '"boolean"'; } - $childLines[] = sprintf('@OA\Property(property="%s", type={%s})', $key, $typesString); + $childLines[] = sprintf('@OA\Property(property="%s", type=%s)', $key, $typesString); continue; } @@ -811,7 +1186,15 @@ protected function buildPropertyAnnotationFromJsonExample(string $propName, arra return ['@OA\Property' => array_merge($propertyLines, $childLines)]; } - protected function buildSchemaAnnotationFromXmlExample(array $xmlArrayObject, string $root = 'result'): array + /** + * Take the deserialised structure of an XML node and build the lines of an OA\Schema annotation object for it. + * + * @param array $xmlArrayObject Nested array of properties of the XML node. + * @param string $root Name of the root element. The default is 'result'. + * + * @return array Collection of potentially nested arrays representing an OA\Property annotation object. + */ + public function buildSchemaAnnotationFromXmlExample(array $xmlArrayObject, string $root = 'result'): array { $lines = [ 'type="object",', @@ -838,7 +1221,15 @@ protected function buildSchemaAnnotationFromXmlExample(array $xmlArrayObject, st return ['@OA\Schema' => $lines]; } - protected function buildPropertyAnnotationFromXmlExample(string $propName, array $values): array + /** + * Take the deserialised structure of an XML node and build the lines of an OA\Property annotation object for it. + * + * @param string $propName Name of the XML node. + * @param array $values Nested array of properties of the XML node. + * + * @return array Collection of potentially nested arrays representing an OA\Property annotation object. + */ + public function buildPropertyAnnotationFromXmlExample(string $propName, array $values): array { $type = 'object'; if ($propName === 'row') { @@ -894,7 +1285,14 @@ protected function buildPropertyAnnotationFromXmlExample(string $propName, array return ['@OA\Property' => array_merge($propertyLines, $childLines)]; } - protected function removeTrailingCommaFromLastLine(&$lines): void + /** + * Take a list of lines and remove the trailing comma from the last line. + * + * @param string[] $lines List of lines for an annotation passed by reference. + * + * @return void + */ + public function removeTrailingCommaFromLastLine(array &$lines): void { if (!empty($lines)) { $last = array_pop($lines); @@ -902,7 +1300,18 @@ protected function removeTrailingCommaFromLastLine(&$lines): void } } - protected function buildLinesForAnnotationObject(string $objectName, array $objectProperties, int $indent = 0): array + /** + * Generic method for building the array of lines for an annotation object. It handles adding the indent based on + * the level. For example, if it's nested under 3 other objects, the indent will be 12 spaces (3 x 4-space tabs). + * + * @param string $objectName The type of object. E.g. OA\Schema or OA\Property + * @param array $objectProperties A nested array of the properties of the object. E.g. type, example, OA\Schema, ... + * @param int $indent The count of indents/tabs based on the nesting the object. E.g. 0 = none & 2 = indented twice. + * + * @return array The lines of the annotation object with correct opening/closing characters (usually parenthesis), + * and proper indentation for each line. + */ + public function buildLinesForAnnotationObject(string $objectName, array $objectProperties, int $indent = 0): array { $indentString = str_repeat(' ', $indent); $innerIndentString = str_repeat(' ', $indent + 1); @@ -938,7 +1347,17 @@ protected function buildLinesForAnnotationObject(string $objectName, array $obje return array_merge([$indentString . $objectName . $openingCharacter], $lines, [$indentString . $closingCharacter . ',']); } - protected function buildSchemaObjectArray(string $type, string $subType = '', string $default = NoDefaultValue::class, string $example = ''): array + /** + * Build the array of lines for the OA\Schema annotation object for a single type. + * + * @param string $type The type of the parameter. E.g. string, integer, number, boolean, array, ... + * @param string $subType This can specify the subtype for arrays. E.g. integer for int[] or string for string[]. + * @param string $default The optional default value for the type. Default is no value. + * @param string $example The optional example value for the type. Default is empty string which indicated no value. + * + * @return array[] + */ + public function buildSchemaObjectArray(string $type, string $subType = '', string $default = NoDefaultValue::class, string $example = ''): array { $schemaMap = ['type="' . $type . '"']; if (($example) !== '') { @@ -962,7 +1381,17 @@ protected function buildSchemaObjectArray(string $type, string $subType = '', st return ['@OA\Schema' => $schemaMap]; } - protected function wrapStringWithQuotes(string $string, string $type, string $quoteCharacter = '"'): string + /** + * Wrap an example of default value string with quotes. E.g. "exampleValue". Depending on the type and the value, + * the quotes may be omitted. + * + * @param string $string Value for the example or default. + * @param string $type The type of the parameter. E.g. string, integer, number, boolean, array, ... + * @param string $quoteCharacter What to wrap the value with, if it should be wrapped. The default is double quote. + * + * @return string + */ + public function wrapStringWithQuotes(string $string, string $type, string $quoteCharacter = '"'): string { if (in_array($type, ['integer', 'boolean', 'array'])) { return $string; @@ -976,7 +1405,15 @@ protected function wrapStringWithQuotes(string $string, string $type, string $qu return "{$quoteCharacter}{$string}{$quoteCharacter}"; } - protected function shouldIncludeDefault(string $type, string $default = NoDefaultValue::class): bool + /** + * Indicates whether a specific parameter should include a default value in its annotation. + * + * @param string $type The type of the parameter. E.g. string, integer, number, boolean, array, ... + * @param string $default The default value from reflection or doc block. + * + * @return bool Whether a default value should be included or not. + */ + public function shouldIncludeDefault(string $type, string $default = NoDefaultValue::class): bool { if ($default === NoDefaultValue::class) { return false; @@ -990,7 +1427,21 @@ protected function shouldIncludeDefault(string $type, string $default = NoDefaul return true; } - protected function buildSchemaObjectArrays(array $typesMap, string $default = '', string $example = ''): array + /** + * Build the array for the OA\Schema annotation object for one or more types. + * + * @param array $typesMap The array of types where the keys are the types and the values are the subtypes, if any. + * E.g. ['string' => null, 'array' => 'integer'] for idSites which can be an array or comma-separated-string of IDs. + * The string key has a value of null because it has no subtype while the array has 'integer' because values should + * be int IDs. + * @param string $default The value to use as the default property of the schema. If it's an empty string, no + * default is set. + * @param string $example The value to use as the example property of the schema. If it's an empty string, no + * example is set. + * + * @return array[] The collection of lines which make up the schema annotation object. + */ + public function buildSchemaObjectArrays(array $typesMap, string $default = '', string $example = ''): array { $schemas = []; foreach ($typesMap as $type => $subType) { @@ -1004,7 +1455,20 @@ protected function buildSchemaObjectArrays(array $typesMap, string $default = '' return ['@OA\Schema' => ['oneOf={' => $schemas]]; } - protected function compileOperationLines(string $path, string $opId, string $plugin, string $method, array $params, array $responses, bool $isPost = false): array + /** + * Build the full array of lines for an OA operation. E.g. OA\Get or OA\Post + * + * @param string $path The operation path. E.g. /index.php?module=API&method=CustomReports.getConfiguredReport + * @param string $opId The string which uniquely identifies the operation across the entire OpenAPI spec. In order + * to avoid potential duplicates, we use the plugin name and method name. E.g. CustomReports.getConfiguredReport + * @param string $plugin The name of the plugin. E.g. CustomReports + * @param array $params The compiled list of method parameters and key information about them, like type. + * @param array $responses compiled list of method expected responses and key information about them, like type. + * @param bool $isPost Indicates whether the operation is a POST. The default is false, meaning it's GET. + * + * @return string[] The array of all the lines of the operation annotation object. + */ + public function compileOperationLines(string $path, string $opId, string $plugin, array $params, array $responses, bool $isPost = false): array { $operationValuesMap = [ 'path="' . $path . '"', @@ -1057,8 +1521,6 @@ protected function compileOperationLines(string $path, string $opId, string $plu $operationValuesMap[] = ['@OA\Response' => $responsePropertyArray]; } } - // TODO - Remove this if it's determined that we won't ever use it - //$operationValuesMap[] = 'x={"runtime"={"entry":"index.php","query":{"module":"API","method":"' . $plugin . '.' . $method . '"}}}'; $lines = $this->buildLinesForAnnotationObject('@OA\\' . ($isPost ? 'Post' : 'Get'), $operationValuesMap); diff --git a/Annotations/GlobalApiComponents.php b/Annotations/GlobalApiComponents.php index 2e2f30b..4f35ea8 100644 --- a/Annotations/GlobalApiComponents.php +++ b/Annotations/GlobalApiComponents.php @@ -323,6 +323,15 @@ * ) * * Parameters specific to DataTables and Views + * @OA\Parameter(parameter="expandedOptional", name="expanded", in="query", + * description="If true, loads all subtables.", required=false, + * @OA\Schema(type="integer", enum={0,1}, example=0, default=0)) + * + * @OA\Parameter(parameter="idSubtableOptional", name="idSubtable", in="query", + * description="An in-database subtable ID.", required=false, + * @OA\Schema(type="integer")) + * + * Parameters specific to DataTables and Views * @OA\Parameter(parameter="flatOptional", name="flat", in="query", * description="Flatten subtables into the parent table.", required=false, * @OA\Schema(type="integer", enum={0,1}, example=0)) @@ -358,7 +367,7 @@ * description="Row index after which rows are removed.", required=false, @OA\Schema(type="integer")) * * @OA\Parameter(parameter="filter_limitOptional", name="filter_limit", in="query", - * description="Maximum rows to return.", required=false, @OA\Schema(type="integer")) + * description="Maximum number of rows to return.", required=false, @OA\Schema(type="integer")) * * @OA\Parameter(parameter="filter_offsetOptional", name="filter_offset", in="query", * description="Row offset.", required=false, @OA\Schema(type="integer")) @@ -384,8 +393,11 @@ * @OA\Parameter(parameter="labelOptional", name="label", in="query", * description="Keep only rows with these label(s). Supports path via '>' and arrays.", required=false, @OA\Schema(type="string")) * + * @OA\Parameter(parameter="idGoalRequired", name="idGoal", in="query", + * description="The ID of a configured goal.", required=true, @OA\Schema(type="integer")) + * * @OA\Parameter(parameter="idGoalOptional", name="idGoal", in="query", - * description="Goal ID or special values (overview/minimal/full table).", required=false, @OA\Schema(type="string")) + * description="The ID of a configured goal.", required=false, @OA\Schema(type="integer")) */ class GlobalApiComponents { diff --git a/tests/Resources/ExampleResponses/CustomAlerts.getAlert.json b/tests/Resources/ExampleResponses/CustomAlerts.getAlert.json new file mode 100644 index 0000000..2a31a00 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomAlerts.getAlert.json @@ -0,0 +1,23 @@ +{ + "idalert": 1, + "name": "Test Alert", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "greater_than", + "metric_matched": 500, + "compared_to": 7, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] +} \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomAlerts.getAlert.xml b/tests/Resources/ExampleResponses/CustomAlerts.getAlert.xml new file mode 100644 index 0000000..92e2fa1 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomAlerts.getAlert.xml @@ -0,0 +1,26 @@ + + + 1 + Test Alert + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + greater_than + 500 + 7 + 1 + + + + + + + 1 + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomAlerts.getAlerts.json b/tests/Resources/ExampleResponses/CustomAlerts.getAlerts.json new file mode 100644 index 0000000..321be39 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomAlerts.getAlerts.json @@ -0,0 +1,48 @@ +[ + { + "idalert": 1, + "name": "Test Alert", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "greater_than", + "metric_matched": 500, + "compared_to": 7, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idalert": 2, + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + } +] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomAlerts.getAlerts.xml b/tests/Resources/ExampleResponses/CustomAlerts.getAlerts.xml new file mode 100644 index 0000000..a3b9ed9 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomAlerts.getAlerts.xml @@ -0,0 +1,53 @@ + + + + 1 + Test Alert + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + greater_than + 500 + 7 + 1 + + + + + + + 1 + + + + 2 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomAlerts.getTriggeredAlerts.json b/tests/Resources/ExampleResponses/CustomAlerts.getTriggeredAlerts.json new file mode 100644 index 0000000..d02ea32 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomAlerts.getTriggeredAlerts.json @@ -0,0 +1,698 @@ +[ + { + "idtriggered": 1, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-09-13 01:55:48", + "ts_last_sent": "2024-09-13 01:58:54", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 2, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-09-13 01:58:48", + "ts_last_sent": "2024-09-13 01:58:54", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 3, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-09-13 02:00:39", + "ts_last_sent": "2024-09-13 02:00:46", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 4, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-09-13 02:55:47", + "ts_last_sent": "2024-09-13 02:55:53", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 5, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-09-16 23:33:41", + "ts_last_sent": null, + "value_old": "500.000", + "value_new": "36.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 6, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-09-18 03:21:11", + "ts_last_sent": "2024-09-18 03:23:05", + "value_old": "36.000", + "value_new": "1.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 9, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-09-18 22:18:55", + "ts_last_sent": "2024-09-18 22:19:02", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 12, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-09-26 01:18:30", + "ts_last_sent": null, + "value_old": "2.000", + "value_new": "1.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 13, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-10-11 02:26:50", + "ts_last_sent": null, + "value_old": "1190.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 14, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2024-11-28 20:34:14", + "ts_last_sent": null, + "value_old": "2.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 15, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-03-27 03:39:26", + "ts_last_sent": "2025-03-27 03:39:26", + "value_old": "4.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 16, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-04-03 20:59:07", + "ts_last_sent": "2025-04-03 20:59:07", + "value_old": "243.000", + "value_new": "110.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 17, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-05-04 11:31:47", + "ts_last_sent": "2025-05-04 11:31:47", + "value_old": "330.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 18, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-05-10 11:31:23", + "ts_last_sent": "2025-05-10 11:31:24", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 19, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-05-16 11:31:19", + "ts_last_sent": "2025-05-16 11:31:19", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 20, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-05-18 11:31:24", + "ts_last_sent": "2025-05-18 11:31:24", + "value_old": "2.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 21, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-05-22 11:31:19", + "ts_last_sent": "2025-05-22 11:31:19", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 22, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-05-25 11:31:24", + "ts_last_sent": "2025-05-25 11:31:25", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 23, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-05-28 11:31:26", + "ts_last_sent": "2025-05-28 11:31:27", + "value_old": "304.000", + "value_new": "3.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 24, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-05-29 11:31:25", + "ts_last_sent": "2025-05-29 11:31:25", + "value_old": "3.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 25, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-07-06 11:31:34", + "ts_last_sent": "2025-07-06 11:31:34", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 26, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-07-16 11:31:35", + "ts_last_sent": "2025-07-16 11:31:36", + "value_old": "357.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 27, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-08-02 11:31:41", + "ts_last_sent": "2025-08-02 11:31:41", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + }, + { + "idtriggered": 28, + "idalert": 2, + "idsite": 1, + "ts_triggered": "2025-08-09 11:31:44", + "ts_last_sent": "2025-08-09 11:31:45", + "value_old": "1.000", + "value_new": "0.000", + "name": "Test Visit Drop Previous Day", + "login": "someUserName", + "period": "day", + "report": "VisitsSummary_get", + "report_condition": null, + "report_matched": null, + "report_mediums": [ + "email" + ], + "metric": "nb_uniq_visitors", + "metric_condition": "percentage_decrease_more_than", + "metric_matched": 20, + "compared_to": 1, + "email_me": 1, + "additional_emails": [], + "phone_numbers": [], + "slack_channel_id": null, + "id_sites": [ + 1 + ] + } +] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomAlerts.getTriggeredAlerts.xml b/tests/Resources/ExampleResponses/CustomAlerts.getTriggeredAlerts.xml new file mode 100644 index 0000000..d79beb9 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomAlerts.getTriggeredAlerts.xml @@ -0,0 +1,747 @@ + + + + 1 + 2 + 1 + 2024-09-13 01:55:48 + 2024-09-13 01:58:54 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 2 + 2 + 1 + 2024-09-13 01:58:48 + 2024-09-13 01:58:54 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 3 + 2 + 1 + 2024-09-13 02:00:39 + 2024-09-13 02:00:46 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 4 + 2 + 1 + 2024-09-13 02:55:47 + 2024-09-13 02:55:53 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 5 + 2 + 1 + 2024-09-16 23:33:41 + + 500.000 + 36.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 6 + 2 + 1 + 2024-09-18 03:21:11 + 2024-09-18 03:23:05 + 36.000 + 1.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 9 + 2 + 1 + 2024-09-18 22:18:55 + 2024-09-18 22:19:02 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 12 + 2 + 1 + 2024-09-26 01:18:30 + + 2.000 + 1.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 13 + 2 + 1 + 2024-10-11 02:26:50 + + 1190.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 14 + 2 + 1 + 2024-11-28 20:34:14 + + 2.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 15 + 2 + 1 + 2025-03-27 03:39:26 + 2025-03-27 03:39:26 + 4.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 16 + 2 + 1 + 2025-04-03 20:59:07 + 2025-04-03 20:59:07 + 243.000 + 110.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 17 + 2 + 1 + 2025-05-04 11:31:47 + 2025-05-04 11:31:47 + 330.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 18 + 2 + 1 + 2025-05-10 11:31:23 + 2025-05-10 11:31:24 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 19 + 2 + 1 + 2025-05-16 11:31:19 + 2025-05-16 11:31:19 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 20 + 2 + 1 + 2025-05-18 11:31:24 + 2025-05-18 11:31:24 + 2.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 21 + 2 + 1 + 2025-05-22 11:31:19 + 2025-05-22 11:31:19 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 22 + 2 + 1 + 2025-05-25 11:31:24 + 2025-05-25 11:31:25 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 23 + 2 + 1 + 2025-05-28 11:31:26 + 2025-05-28 11:31:27 + 304.000 + 3.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 24 + 2 + 1 + 2025-05-29 11:31:25 + 2025-05-29 11:31:25 + 3.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 25 + 2 + 1 + 2025-07-06 11:31:34 + 2025-07-06 11:31:34 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 26 + 2 + 1 + 2025-07-16 11:31:35 + 2025-07-16 11:31:36 + 357.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 27 + 2 + 1 + 2025-08-02 11:31:41 + 2025-08-02 11:31:41 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + + 28 + 2 + 1 + 2025-08-09 11:31:44 + 2025-08-09 11:31:45 + 1.000 + 0.000 + Test Visit Drop Previous Day + someUserName + day + VisitsSummary_get + + + + email + + nb_uniq_visitors + percentage_decrease_more_than + 20 + 1 + 1 + + + + + + + 1 + + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getAvailableExtractionDimensions.json b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableExtractionDimensions.json new file mode 100644 index 0000000..575e0ba --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableExtractionDimensions.json @@ -0,0 +1,14 @@ +[ + { + "value": "url", + "name": "Page URL" + }, + { + "value": "urlparam", + "name": "Page URL Parameter" + }, + { + "value": "action_name", + "name": "Page Title" + } +] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getAvailableExtractionDimensions.tsv b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableExtractionDimensions.tsv new file mode 100644 index 0000000..f6d25aa --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableExtractionDimensions.tsv @@ -0,0 +1,4 @@ +value name +url Page URL +urlparam Page URL Parameter +action_name Page Title \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getAvailableExtractionDimensions.xml b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableExtractionDimensions.xml new file mode 100644 index 0000000..7a39759 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableExtractionDimensions.xml @@ -0,0 +1,15 @@ + + + + url + Page URL + + + urlparam + Page URL Parameter + + + action_name + Page Title + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getAvailableScopes.json b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableScopes.json new file mode 100644 index 0000000..ed0b1d1 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableScopes.json @@ -0,0 +1,18 @@ +[ + { + "value": "visit", + "name": "Visit", + "numSlotsAvailable": 15, + "numSlotsUsed": 1, + "numSlotsLeft": 14, + "supportsExtractions": false + }, + { + "value": "action", + "name": "Action", + "numSlotsAvailable": 15, + "numSlotsUsed": 5, + "numSlotsLeft": 10, + "supportsExtractions": true + } +] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getAvailableScopes.tsv b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableScopes.tsv new file mode 100644 index 0000000..e3d8171 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableScopes.tsv @@ -0,0 +1,3 @@ +value name numSlotsAvailable numSlotsUsed numSlotsLeft supportsExtractions +visit Visit 15 1 14 0 +action Action 15 5 10 1 \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getAvailableScopes.xml b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableScopes.xml new file mode 100644 index 0000000..24bb6c6 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getAvailableScopes.xml @@ -0,0 +1,19 @@ + + + + visit + Visit + 15 + 1 + 14 + 0 + + + action + Action + 15 + 5 + 10 + 1 + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getConfiguredCustomDimensions.json b/tests/Resources/ExampleResponses/CustomDimensions.getConfiguredCustomDimensions.json new file mode 100644 index 0000000..fa3ee12 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getConfiguredCustomDimensions.json @@ -0,0 +1,87 @@ +[ + { + "idcustomdimension": "1", + "idsite": "1", + "name": "User Type", + "index": "1", + "scope": "visit", + "active": true, + "extractions": [], + "case_sensitive": true + }, + { + "idcustomdimension": "2", + "idsite": "1", + "name": "Page Author", + "index": "1", + "scope": "action", + "active": true, + "extractions": [ + { + "dimension": "url", + "pattern": "" + } + ], + "case_sensitive": true + }, + { + "idcustomdimension": "3", + "idsite": "1", + "name": "Page Age", + "index": "2", + "scope": "action", + "active": false, + "extractions": [ + { + "dimension": "url", + "pattern": "" + } + ], + "case_sensitive": true + }, + { + "idcustomdimension": "4", + "idsite": "1", + "name": "Page Location", + "index": "3", + "scope": "action", + "active": true, + "extractions": [ + { + "dimension": "url", + "pattern": "" + } + ], + "case_sensitive": true + }, + { + "idcustomdimension": "5", + "idsite": "1", + "name": "Page Type", + "index": "4", + "scope": "action", + "active": true, + "extractions": [ + { + "dimension": "url", + "pattern": "" + } + ], + "case_sensitive": true + }, + { + "idcustomdimension": "6", + "idsite": "1", + "name": "Diving Rating", + "index": "5", + "scope": "action", + "active": false, + "extractions": [ + { + "dimension": "url", + "pattern": "" + } + ], + "case_sensitive": true + } +] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getConfiguredCustomDimensions.xml b/tests/Resources/ExampleResponses/CustomDimensions.getConfiguredCustomDimensions.xml new file mode 100644 index 0000000..4902a4a --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getConfiguredCustomDimensions.xml @@ -0,0 +1,89 @@ + + + + 1 + 1 + User Type + 1 + visit + 1 + + + 1 + + + 2 + 1 + Page Author + 1 + action + 1 + + + url + + + + 1 + + + 3 + 1 + Page Age + 2 + action + 0 + + + url + + + + 1 + + + 4 + 1 + Page Location + 3 + action + 1 + + + url + + + + 1 + + + 5 + 1 + Page Type + 4 + action + 1 + + + url + + + + 1 + + + 6 + 1 + Diving Rating + 5 + action + 0 + + + url + + + + 1 + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getCustomDimension.json b/tests/Resources/ExampleResponses/CustomDimensions.getCustomDimension.json new file mode 100644 index 0000000..afb120b --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getCustomDimension.json @@ -0,0 +1,35 @@ +[ + { + "label": "guest", + "nb_uniq_visitors": "140", + "nb_visits": "140", + "nb_actions": "307", + "max_actions": 29, + "sum_visit_length": "17864", + "bounce_count": "97", + "nb_visits_converted": "11", + "goals": { + "idgoal=6": { + "nb_conversions": 1, + "nb_visits_converted": 1, + "revenue": 2 + }, + "idgoal=7": { + "nb_conversions": 8, + "nb_visits_converted": 8, + "revenue": 8 + }, + "idgoal=8": { + "nb_conversions": 3, + "nb_visits_converted": 3, + "revenue": 0 + } + }, + "nb_conversions": 12, + "revenue": 10, + "avg_time_on_site": 128, + "bounce_rate": "69%", + "nb_actions_per_visit": 2.2, + "segment": "dimension1==guest" + } +] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getCustomDimension.tsv b/tests/Resources/ExampleResponses/CustomDimensions.getCustomDimension.tsv new file mode 100644 index 0000000..9afbb95 --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getCustomDimension.tsv @@ -0,0 +1,2 @@ +label nb_uniq_visitors nb_visits nb_actions max_actions sum_visit_length bounce_count nb_visits_converted goals_idgoal=6_nb_conversions goals_idgoal=6_nb_visits_converted goals_idgoal=6_revenue goals_idgoal=7_nb_conversions goals_idgoal=7_nb_visits_converted goals_idgoal=7_revenue goals_idgoal=8_nb_conversions goals_idgoal=8_nb_visits_converted goals_idgoal=8_revenue nb_conversions revenue avg_time_on_site bounce_rate nb_actions_per_visit metadata_segment +guest 140 140 307 29 17864 97 11 1 1 2 8 8 8 3 3 0 12 10 128 69% 2.2 dimension1==guest \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/CustomDimensions.getCustomDimension.xml b/tests/Resources/ExampleResponses/CustomDimensions.getCustomDimension.xml new file mode 100644 index 0000000..cc78beb --- /dev/null +++ b/tests/Resources/ExampleResponses/CustomDimensions.getCustomDimension.xml @@ -0,0 +1,36 @@ + + + + + 140 + 140 + 307 + 29 + 17864 + 97 + 11 + + + 1 + 1 + 2 + + + 8 + 8 + 8 + + + 3 + 3 + 0 + + + 12 + 10 + 128 + 69% + 2.2 + dimension1==guest + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getAvailableLogReaders.json b/tests/Resources/ExampleResponses/LogViewer.getAvailableLogReaders.json new file mode 100644 index 0000000..cc86e4f --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getAvailableLogReaders.json @@ -0,0 +1 @@ +["file","database"] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getAvailableLogReaders.tsv b/tests/Resources/ExampleResponses/LogViewer.getAvailableLogReaders.tsv new file mode 100644 index 0000000..b68212d --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getAvailableLogReaders.tsv @@ -0,0 +1,2 @@ +file +database \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getAvailableLogReaders.xml b/tests/Resources/ExampleResponses/LogViewer.getAvailableLogReaders.xml new file mode 100644 index 0000000..cf23cc6 --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getAvailableLogReaders.xml @@ -0,0 +1,5 @@ + + + file + database + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getConfiguredLogReaders.json b/tests/Resources/ExampleResponses/LogViewer.getConfiguredLogReaders.json new file mode 100644 index 0000000..6272a5d --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getConfiguredLogReaders.json @@ -0,0 +1 @@ +["file"] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getConfiguredLogReaders.tsv b/tests/Resources/ExampleResponses/LogViewer.getConfiguredLogReaders.tsv new file mode 100644 index 0000000..1a010b1 --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getConfiguredLogReaders.tsv @@ -0,0 +1 @@ +file \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getConfiguredLogReaders.xml b/tests/Resources/ExampleResponses/LogViewer.getConfiguredLogReaders.xml new file mode 100644 index 0000000..67d4d5d --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getConfiguredLogReaders.xml @@ -0,0 +1,4 @@ + + + file + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getLogConfig.json b/tests/Resources/ExampleResponses/LogViewer.getLogConfig.json new file mode 100644 index 0000000..9b9ba44 --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getLogConfig.json @@ -0,0 +1,9 @@ +{ + "log_writers": [ + "screen", + "file" + ], + "log_level": "WARN", + "logger_file_path": "tmp\/logs\/matomo.log", + "logger_syslog_ident": "matomo" +} \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getLogConfig.xml b/tests/Resources/ExampleResponses/LogViewer.getLogConfig.xml new file mode 100644 index 0000000..c62062d --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getLogConfig.xml @@ -0,0 +1,10 @@ + + + + screen + file + + WARN + tmp/logs/matomo.log + matomo + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getLogEntries.json b/tests/Resources/ExampleResponses/LogViewer.getLogEntries.json new file mode 100644 index 0000000..6b1ca9e --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getLogEntries.json @@ -0,0 +1,72 @@ +[ + { + "severity": "", + "tag": "", + "datetime": "", + "requestId": "", + "message": "" + }, + { + "severity": "ERROR", + "tag": "API", + "datetime": "2025-09-19 07:55:22 UTC", + "requestId": "1bc99", + "message": "#17 {main} [Query: ?module=API&method=LogViewer.getLogConfig&format=Tsv&token_auth=removed&convertToUnicode=0&hideIdSubDatable=1, CLI mode: 0]" + }, + { + "severity": "ERROR", + "tag": "API", + "datetime": "2025-09-19 07:55:22 UTC", + "requestId": "1bc99", + "message": "#16 \/index.php(25): require_once('...')" + }, + { + "severity": "ERROR", + "tag": "API", + "datetime": "2025-09-19 07:55:22 UTC", + "requestId": "1bc99", + "message": "#15 \/core\/dispatch.php(33): Piwik\\FrontController->dispatch()" + }, + { + "severity": "ERROR", + "tag": "API", + "datetime": "2025-09-19 07:55:22 UTC", + "requestId": "1bc99", + "message": "#14 \/core\/FrontController.php(170): Piwik\\FrontController->doDispatch()" + }, + { + "severity": "ERROR", + "tag": "API", + "datetime": "2025-09-19 07:55:22 UTC", + "requestId": "1bc99", + "message": "#13 \/core\/FrontController.php(646): call_user_func_array()" + }, + { + "severity": "ERROR", + "tag": "API", + "datetime": "2025-09-19 07:55:22 UTC", + "requestId": "1bc99", + "message": "#12 [internal function]: Piwik\\Plugins\\API\\Controller->index()" + }, + { + "severity": "ERROR", + "tag": "API", + "datetime": "2025-09-19 07:55:22 UTC", + "requestId": "1bc99", + "message": "#11 \/plugins\/API\/Controller.php(48): Piwik\\API\\Request->process()" + }, + { + "severity": "ERROR", + "tag": "API", + "datetime": "2025-09-19 07:55:22 UTC", + "requestId": "1bc99", + "message": "#10 \/core\/API\/Request.php(282): Piwik\\Context::executeWithQueryParameters()" + }, + { + "severity": "ERROR", + "tag": "API", + "datetime": "2025-09-19 07:55:22 UTC", + "requestId": "1bc99", + "message": "#9 \/core\/Context.php(29): Piwik\\API\\Request->{closure:Piwik\\API\\Request::process():282}()" + } +] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getLogEntries.tsv b/tests/Resources/ExampleResponses/LogViewer.getLogEntries.tsv new file mode 100644 index 0000000..6c0842f --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getLogEntries.tsv @@ -0,0 +1,11 @@ +severity tag datetime requestId message + +ERROR API 2025-09-19 08:04:51 UTC 2904f "#17 {main} [Query: ?module=API&method=LogViewer.getLogConfig&format=Tsv&token_auth=removed&convertToUnicode=0&hideIdSubDatable=1, CLI mode: 0]" +ERROR API 2025-09-19 08:04:51 UTC 2904f #16 /index.php(25): require_once('...') +ERROR API 2025-09-19 08:04:51 UTC 2904f #15 /core/dispatch.php(33): Piwik\FrontController->dispatch() +ERROR API 2025-09-19 08:04:51 UTC 2904f #14 /core/FrontController.php(170): Piwik\FrontController->doDispatch() +ERROR API 2025-09-19 08:04:51 UTC 2904f #13 /core/FrontController.php(646): call_user_func_array() +ERROR API 2025-09-19 08:04:51 UTC 2904f #12 [internal function]: Piwik\Plugins\API\Controller->index() +ERROR API 2025-09-19 08:04:51 UTC 2904f #11 /plugins/API/Controller.php(48): Piwik\API\Request->process() +ERROR API 2025-09-19 08:04:51 UTC 2904f #10 /core/API/Request.php(282): Piwik\Context::executeWithQueryParameters() +ERROR API 2025-09-19 08:04:51 UTC 2904f #9 /core/Context.php(29): Piwik\API\Request->{closure:Piwik\API\Request::process():282}() \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/LogViewer.getLogEntries.xml b/tests/Resources/ExampleResponses/LogViewer.getLogEntries.xml new file mode 100644 index 0000000..798d35c --- /dev/null +++ b/tests/Resources/ExampleResponses/LogViewer.getLogEntries.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + ERROR + API + 2025-09-19 07:55:22 UTC + 1bc99 + #17 {main} [Query: ?module=API&method=LogViewer.getLogConfig&format=Tsv&token_auth=removed& + convertToUnicode=0&hideIdSubDatable=1, CLI mode: 0] + + + + ERROR + API + 2025-09-19 07:55:22 UTC + 1bc99 + #16 /index.php(25): require_once('...') + + + ERROR + API + 2025-09-19 07:55:22 UTC + 1bc99 + #15 /core/dispatch.php(33): Piwik\FrontController->dispatch() + + + ERROR + API + 2025-09-19 07:55:22 UTC + 1bc99 + #14 /core/FrontController.php(170): Piwik\FrontController->doDispatch() + + + ERROR + API + 2025-09-19 07:55:22 UTC + 1bc99 + #13 /core/FrontController.php(646): call_user_func_array() + + + ERROR + API + 2025-09-19 07:55:22 UTC + 1bc99 + #12 [internal function]: Piwik\Plugins\API\Controller->index() + + + ERROR + API + 2025-09-19 07:55:22 UTC + 1bc99 + #11 /plugins/API/Controller.php(48): Piwik\API\Request->process() + + + ERROR + API + 2025-09-19 07:55:22 UTC + 1bc99 + #10 /core/API/Request.php(282): Piwik\Context::executeWithQueryParameters() + + + ERROR + API + 2025-09-19 07:55:22 UTC + 1bc99 + #9 /core/Context.php(29): Piwik\API\Request->{closure:Piwik\API\Request::process():282}() + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getKeyword.json b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getKeyword.json new file mode 100644 index 0000000..4878a50 --- /dev/null +++ b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getKeyword.json @@ -0,0 +1,69 @@ +[ + { + "label": "more", + "nb_uniq_visitors": 1, + "nb_visits": 2, + "nb_actions": "12", + "nb_users": 1, + "max_actions": 8, + "sum_visit_length": "5", + "bounce_count": "0", + "nb_visits_converted": "2", + "goals": { + "idgoal=ecommerceOrder": { + "nb_conversions": 1, + "nb_visits_converted": 1, + "revenue": 232.56, + "revenue_subtotal": 204, + "revenue_tax": 38.76, + "revenue_shipping": 10.2, + "revenue_discount": 20.4, + "items": 3 + }, + "idgoal=1": { + "nb_conversions": 1, + "nb_visits_converted": 1, + "revenue": 0 + } + }, + "nb_conversions": 2, + "revenue": 232.56, + "segment": "campaignKeyword==more" + }, + { + "label": "learnmore", + "nb_uniq_visitors": 1, + "nb_visits": 1, + "nb_actions": "1", + "nb_users": 1, + "max_actions": 1, + "sum_visit_length": "0", + "bounce_count": "1", + "nb_visits_converted": "0", + "segment": "campaignKeyword==learnmore" + }, + { + "label": "ordernow", + "nb_uniq_visitors": 1, + "nb_visits": 1, + "nb_actions": "6", + "nb_users": 0, + "max_actions": 6, + "sum_visit_length": "1", + "bounce_count": "0", + "nb_visits_converted": "0", + "segment": "campaignKeyword==ordernow" + }, + { + "label": "www.facebook.com", + "nb_uniq_visitors": 1, + "nb_visits": 1, + "nb_actions": "1", + "nb_users": 1, + "max_actions": 1, + "sum_visit_length": "0", + "bounce_count": "1", + "nb_visits_converted": "0", + "segment": "campaignKeyword==www.facebook.com" + } +] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getKeyword.tsv b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getKeyword.tsv new file mode 100644 index 0000000..cb090b6 --- /dev/null +++ b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getKeyword.tsv @@ -0,0 +1,5 @@ +label nb_uniq_visitors nb_visits nb_actions nb_users max_actions sum_visit_length bounce_count nb_visits_converted goals_idgoal=ecommerceOrder_nb_conversions goals_idgoal=ecommerceOrder_nb_visits_converted goals_idgoal=ecommerceOrder_revenue goals_idgoal=ecommerceOrder_revenue_subtotal goals_idgoal=ecommerceOrder_revenue_tax goals_idgoal=ecommerceOrder_revenue_shipping goals_idgoal=ecommerceOrder_revenue_discount goals_idgoal=ecommerceOrder_items goals_idgoal=1_nb_conversions goals_idgoal=1_nb_visits_converted goals_idgoal=1_revenue nb_conversions revenue metadata_segment +more 1 2 12 1 8 5 0 2 1 1 232.56 204 38.76 10.2 20.4 3 1 1 0 2 232.56 campaignKeyword==more +learnmore 1 1 1 1 1 0 1 0 campaignKeyword==learnmore +ordernow 1 1 6 0 6 1 0 0 campaignKeyword==ordernow +www.facebook.com 1 1 1 1 1 0 1 0 campaignKeyword==www.facebook.com \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getKeyword.xml b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getKeyword.xml new file mode 100644 index 0000000..bc4b055 --- /dev/null +++ b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getKeyword.xml @@ -0,0 +1,70 @@ + + + + + 1 + 2 + 12 + 1 + 8 + 5 + 0 + 2 + + + 1 + 1 + 232.56 + 204 + 38.76 + 10.2 + 20.4 + 3 + + + 1 + 1 + 0 + + + 2 + 232.56 + campaignKeyword==more + + + + 1 + 1 + 1 + 1 + 1 + 0 + 1 + 0 + campaignKeyword==learnmore + + + + 1 + 1 + 6 + 0 + 6 + 1 + 0 + 0 + campaignKeyword==ordernow + + + + 1 + 1 + 1 + 1 + 1 + 0 + 1 + 0 + campaignKeyword==www.facebook.com + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getName.json b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getName.json new file mode 100644 index 0000000..bc2e135 --- /dev/null +++ b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getName.json @@ -0,0 +1,45 @@ +[ + { + "label": "email-nov2011", + "nb_uniq_visitors": 3, + "nb_visits": 4, + "nb_actions": 19, + "nb_users": 2, + "max_actions": 8, + "sum_visit_length": 6, + "bounce_count": 1, + "nb_visits_converted": 2, + "goals": { + "idgoal=ecommerceOrder": { + "nb_conversions": 1, + "nb_visits_converted": 1, + "revenue": 232.56, + "revenue_subtotal": 204, + "revenue_tax": 38.76, + "revenue_shipping": 10.2, + "revenue_discount": 20.4, + "items": 3 + }, + "idgoal=1": { + "nb_conversions": 1, + "nb_visits_converted": 1, + "revenue": 0 + } + }, + "nb_conversions": 2, + "revenue": 232.56, + "segment": "campaignName==email-nov2011" + }, + { + "label": "google-ads-campaign", + "nb_uniq_visitors": 2, + "nb_visits": 2, + "nb_actions": 2, + "nb_users": 2, + "max_actions": 1, + "sum_visit_length": 0, + "bounce_count": 2, + "nb_visits_converted": 0, + "segment": "campaignName==google-ads-campaign" + } +] \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getName.tsv b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getName.tsv new file mode 100644 index 0000000..aa96dde --- /dev/null +++ b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getName.tsv @@ -0,0 +1,3 @@ +label nb_uniq_visitors nb_visits nb_actions nb_users max_actions sum_visit_length bounce_count nb_visits_converted goals_idgoal=ecommerceOrder_nb_conversions goals_idgoal=ecommerceOrder_nb_visits_converted goals_idgoal=ecommerceOrder_revenue goals_idgoal=ecommerceOrder_revenue_subtotal goals_idgoal=ecommerceOrder_revenue_tax goals_idgoal=ecommerceOrder_revenue_shipping goals_idgoal=ecommerceOrder_revenue_discount goals_idgoal=ecommerceOrder_items goals_idgoal=1_nb_conversions goals_idgoal=1_nb_visits_converted goals_idgoal=1_revenue nb_conversions revenue metadata_segment +email-nov2011 3 4 19 2 8 6 1 2 1 1 232.56 204 38.76 10.2 20.4 3 1 1 0 2 232.56 campaignName==email-nov2011 +google-ads-campaign 2 2 2 2 1 0 2 0 campaignName==google-ads-campaign \ No newline at end of file diff --git a/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getName.xml b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getName.xml new file mode 100644 index 0000000..7650320 --- /dev/null +++ b/tests/Resources/ExampleResponses/MarketingCampaignsReporting.getName.xml @@ -0,0 +1,46 @@ + + + + + 3 + 4 + 19 + 2 + 8 + 6 + 1 + 2 + + + 1 + 1 + 232.56 + 204 + 38.76 + 10.2 + 20.4 + 3 + + + 1 + 1 + 0 + + + 2 + 232.56 + campaignName==email-nov2011 + + + + 2 + 2 + 2 + 2 + 1 + 0 + 2 + 0 + campaignName==google-ads-campaign + + \ No newline at end of file diff --git a/tests/Resources/ExampleResponsesNormalised/ExamplesFromDemoByType.json b/tests/Resources/ExampleResponsesNormalised/ExamplesFromDemoByType.json new file mode 100644 index 0000000..7d25103 --- /dev/null +++ b/tests/Resources/ExampleResponsesNormalised/ExamplesFromDemoByType.json @@ -0,0 +1,16 @@ +{ + "CustomDimensions.getCustomDimension": { + "xml": "{\"row\":{\"label\":\"guest\",\"nb_uniq_visitors\":\"140\",\"nb_visits\":\"140\",\"nb_actions\":\"307\",\"max_actions\":\"29\",\"sum_visit_length\":\"17864\",\"bounce_count\":\"97\",\"nb_visits_converted\":\"11\",\"goals\":{\"row\":[{\"nb_conversions\":\"1\",\"nb_visits_converted\":\"1\",\"revenue\":\"2\"},{\"nb_conversions\":\"8\",\"nb_visits_converted\":\"8\",\"revenue\":\"8\"},{\"nb_conversions\":\"3\",\"nb_visits_converted\":\"3\",\"revenue\":\"0\"}]},\"nb_conversions\":\"12\",\"revenue\":\"10\",\"avg_time_on_site\":\"128\",\"bounce_rate\":\"69%\",\"nb_actions_per_visit\":\"2.2\",\"segment\":\"dimension1==guest\"}}", + "json": "[{\"label\":\"guest\",\"nb_uniq_visitors\":\"140\",\"nb_visits\":\"140\",\"nb_actions\":\"307\",\"max_actions\":29,\"sum_visit_length\":\"17864\",\"bounce_count\":\"97\",\"nb_visits_converted\":\"11\",\"goals\":{\"idgoal=6\":{\"nb_conversions\":1,\"nb_visits_converted\":1,\"revenue\":2},\"idgoal=7\":{\"nb_conversions\":8,\"nb_visits_converted\":8,\"revenue\":8},\"idgoal=8\":{\"nb_conversions\":3,\"nb_visits_converted\":3,\"revenue\":0}},\"nb_conversions\":12,\"revenue\":10,\"avg_time_on_site\":128,\"bounce_rate\":\"69%\",\"nb_actions_per_visit\":2.2,\"segment\":\"dimension1==guest\"}]", + "tsv": "label\tnb_uniq_visitors\tnb_visits\tnb_actions\tmax_actions\tsum_visit_length\tbounce_count\tnb_visits_converted\tgoals_idgoal=6_nb_conversions\tgoals_idgoal=6_nb_visits_converted\tgoals_idgoal=6_revenue\tgoals_idgoal=7_nb_conversions\tgoals_idgoal=7_nb_visits_converted\tgoals_idgoal=7_revenue\tgoals_idgoal=8_nb_conversions\tgoals_idgoal=8_nb_visits_converted\tgoals_idgoal=8_revenue\tnb_conversions\trevenue\tavg_time_on_site\tbounce_rate\tnb_actions_per_visit\tmetadata_segment\nguest\t140\t140\t307\t29\t17864\t97\t11\t1\t1\t2\t8\t8\t8\t3\t3\t0\t12\t10\t128\t69%\t2.2\tdimension1==guest" + }, + "CustomDimensions.getConfiguredCustomDimensions": { + "xml": "{\"row\":[{\"idcustomdimension\":\"1\",\"idsite\":\"1\",\"name\":\"User Type\",\"index\":\"1\",\"scope\":\"visit\",\"active\":\"1\",\"extractions\":\"\",\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"2\",\"idsite\":\"1\",\"name\":\"Page Author\",\"index\":\"1\",\"scope\":\"action\",\"active\":\"1\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"3\",\"idsite\":\"1\",\"name\":\"Page Age\",\"index\":\"2\",\"scope\":\"action\",\"active\":\"0\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"4\",\"idsite\":\"1\",\"name\":\"Page Location\",\"index\":\"3\",\"scope\":\"action\",\"active\":\"1\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"5\",\"idsite\":\"1\",\"name\":\"Page Type\",\"index\":\"4\",\"scope\":\"action\",\"active\":\"1\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"6\",\"idsite\":\"1\",\"name\":\"Diving Rating\",\"index\":\"5\",\"scope\":\"action\",\"active\":\"0\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"}]}", + "json": "[{\"idcustomdimension\":\"1\",\"idsite\":\"1\",\"name\":\"User Type\",\"index\":\"1\",\"scope\":\"visit\",\"active\":true,\"extractions\":[],\"case_sensitive\":true},{\"idcustomdimension\":\"2\",\"idsite\":\"1\",\"name\":\"Page Author\",\"index\":\"1\",\"scope\":\"action\",\"active\":true,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true},{\"idcustomdimension\":\"3\",\"idsite\":\"1\",\"name\":\"Page Age\",\"index\":\"2\",\"scope\":\"action\",\"active\":false,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true},{\"idcustomdimension\":\"4\",\"idsite\":\"1\",\"name\":\"Page Location\",\"index\":\"3\",\"scope\":\"action\",\"active\":true,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true},{\"idcustomdimension\":\"5\",\"idsite\":\"1\",\"name\":\"Page Type\",\"index\":\"4\",\"scope\":\"action\",\"active\":true,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true},{\"idcustomdimension\":\"6\",\"idsite\":\"1\",\"name\":\"Diving Rating\",\"index\":\"5\",\"scope\":\"action\",\"active\":false,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true}]" + }, + "CustomDimensions.getAvailableScopes": { + "xml": "{\"row\":[{\"value\":\"visit\",\"name\":\"Visit\",\"numSlotsAvailable\":\"15\",\"numSlotsUsed\":\"1\",\"numSlotsLeft\":\"14\",\"supportsExtractions\":\"0\"},{\"value\":\"action\",\"name\":\"Action\",\"numSlotsAvailable\":\"15\",\"numSlotsUsed\":\"5\",\"numSlotsLeft\":\"10\",\"supportsExtractions\":\"1\"}]}", + "json": "[{\"value\":\"visit\",\"name\":\"Visit\",\"numSlotsAvailable\":15,\"numSlotsUsed\":1,\"numSlotsLeft\":14,\"supportsExtractions\":false},{\"value\":\"action\",\"name\":\"Action\",\"numSlotsAvailable\":15,\"numSlotsUsed\":5,\"numSlotsLeft\":10,\"supportsExtractions\":true}]", + "tsv": "value\tname\tnumSlotsAvailable\tnumSlotsUsed\tnumSlotsLeft\tsupportsExtractions\nvisit\tVisit\t15\t1\t14\t0\naction\tAction\t15\t5\t10\t1" + } +} \ No newline at end of file diff --git a/tests/Resources/ExampleResponsesNormalised/ExamplesFromLocalByType.json b/tests/Resources/ExampleResponsesNormalised/ExamplesFromLocalByType.json new file mode 100644 index 0000000..cdfe517 --- /dev/null +++ b/tests/Resources/ExampleResponsesNormalised/ExamplesFromLocalByType.json @@ -0,0 +1,92 @@ +{ + "MarketingCampaignsReporting.getId": { + "xml": "", + "json": "", + "tsv": "" + }, + "MarketingCampaignsReporting.getName": { + "xml": "{\"row\":[{\"label\":\"email-nov2011\",\"nb_uniq_visitors\":\"3\",\"nb_visits\":\"4\",\"nb_actions\":\"19\",\"nb_users\":\"2\",\"max_actions\":\"8\",\"sum_visit_length\":\"6\",\"bounce_count\":\"1\",\"nb_visits_converted\":\"2\",\"goals\":{\"row\":[{\"nb_conversions\":\"1\",\"nb_visits_converted\":\"1\",\"revenue\":\"232.56\",\"revenue_subtotal\":\"204\",\"revenue_tax\":\"38.76\",\"revenue_shipping\":\"10.2\",\"revenue_discount\":\"20.4\",\"items\":\"3\"},{\"nb_conversions\":\"1\",\"nb_visits_converted\":\"1\",\"revenue\":\"0\"}]},\"nb_conversions\":\"2\",\"revenue\":\"232.56\",\"segment\":\"campaignName==email-nov2011\"},{\"label\":\"google-ads-campaign\",\"nb_uniq_visitors\":\"2\",\"nb_visits\":\"2\",\"nb_actions\":\"2\",\"nb_users\":\"2\",\"max_actions\":\"1\",\"sum_visit_length\":\"0\",\"bounce_count\":\"2\",\"nb_visits_converted\":\"0\",\"segment\":\"campaignName==google-ads-campaign\"}]}", + "json": "[{\"label\":\"email-nov2011\",\"nb_uniq_visitors\":3,\"nb_visits\":4,\"nb_actions\":19,\"nb_users\":2,\"max_actions\":8,\"sum_visit_length\":6,\"bounce_count\":1,\"nb_visits_converted\":2,\"goals\":{\"idgoal=ecommerceOrder\":{\"nb_conversions\":1,\"nb_visits_converted\":1,\"revenue\":232.56,\"revenue_subtotal\":204,\"revenue_tax\":38.76,\"revenue_shipping\":10.2,\"revenue_discount\":20.4,\"items\":3},\"idgoal=1\":{\"nb_conversions\":1,\"nb_visits_converted\":1,\"revenue\":0}},\"nb_conversions\":2,\"revenue\":232.56,\"segment\":\"campaignName==email-nov2011\"},{\"label\":\"google-ads-campaign\",\"nb_uniq_visitors\":2,\"nb_visits\":2,\"nb_actions\":2,\"nb_users\":2,\"max_actions\":1,\"sum_visit_length\":0,\"bounce_count\":2,\"nb_visits_converted\":0,\"segment\":\"campaignName==google-ads-campaign\"}]", + "tsv": "label\tnb_uniq_visitors\tnb_visits\tnb_actions\tnb_users\tmax_actions\tsum_visit_length\tbounce_count\tnb_visits_converted\tgoals_idgoal=ecommerceOrder_nb_conversions\tgoals_idgoal=ecommerceOrder_nb_visits_converted\tgoals_idgoal=ecommerceOrder_revenue\tgoals_idgoal=ecommerceOrder_revenue_subtotal\tgoals_idgoal=ecommerceOrder_revenue_tax\tgoals_idgoal=ecommerceOrder_revenue_shipping\tgoals_idgoal=ecommerceOrder_revenue_discount\tgoals_idgoal=ecommerceOrder_items\tgoals_idgoal=1_nb_conversions\tgoals_idgoal=1_nb_visits_converted\tgoals_idgoal=1_revenue\tnb_conversions\trevenue\tmetadata_segment\nemail-nov2011\t3\t4\t19\t2\t8\t6\t1\t2\t1\t1\t232.56\t204\t38.76\t10.2\t20.4\t3\t1\t1\t0\t2\t232.56\tcampaignName==email-nov2011\ngoogle-ads-campaign\t2\t2\t2\t2\t1\t0\t2\t0\t\t\t\t\t\t\t\t\t\t\t\t\t\tcampaignName==google-ads-campaign" + }, + "MarketingCampaignsReporting.getKeyword": { + "xml": "{\"row\":[{\"label\":\"more\",\"nb_uniq_visitors\":\"1\",\"nb_visits\":\"2\",\"nb_actions\":\"12\",\"nb_users\":\"1\",\"max_actions\":\"8\",\"sum_visit_length\":\"5\",\"bounce_count\":\"0\",\"nb_visits_converted\":\"2\",\"goals\":{\"row\":[{\"nb_conversions\":\"1\",\"nb_visits_converted\":\"1\",\"revenue\":\"232.56\",\"revenue_subtotal\":\"204\",\"revenue_tax\":\"38.76\",\"revenue_shipping\":\"10.2\",\"revenue_discount\":\"20.4\",\"items\":\"3\"},{\"nb_conversions\":\"1\",\"nb_visits_converted\":\"1\",\"revenue\":\"0\"}]},\"nb_conversions\":\"2\",\"revenue\":\"232.56\",\"segment\":\"campaignKeyword==more\"},{\"label\":\"learnmore\",\"nb_uniq_visitors\":\"1\",\"nb_visits\":\"1\",\"nb_actions\":\"1\",\"nb_users\":\"1\",\"max_actions\":\"1\",\"sum_visit_length\":\"0\",\"bounce_count\":\"1\",\"nb_visits_converted\":\"0\",\"segment\":\"campaignKeyword==learnmore\"},{\"label\":\"ordernow\",\"nb_uniq_visitors\":\"1\",\"nb_visits\":\"1\",\"nb_actions\":\"6\",\"nb_users\":\"0\",\"max_actions\":\"6\",\"sum_visit_length\":\"1\",\"bounce_count\":\"0\",\"nb_visits_converted\":\"0\",\"segment\":\"campaignKeyword==ordernow\"},{\"label\":\"www.facebook.com\",\"nb_uniq_visitors\":\"1\",\"nb_visits\":\"1\",\"nb_actions\":\"1\",\"nb_users\":\"1\",\"max_actions\":\"1\",\"sum_visit_length\":\"0\",\"bounce_count\":\"1\",\"nb_visits_converted\":\"0\",\"segment\":\"campaignKeyword==www.facebook.com\"}]}", + "json": "[{\"label\":\"more\",\"nb_uniq_visitors\":1,\"nb_visits\":2,\"nb_actions\":\"12\",\"nb_users\":1,\"max_actions\":8,\"sum_visit_length\":\"5\",\"bounce_count\":\"0\",\"nb_visits_converted\":\"2\",\"goals\":{\"idgoal=ecommerceOrder\":{\"nb_conversions\":1,\"nb_visits_converted\":1,\"revenue\":232.56,\"revenue_subtotal\":204,\"revenue_tax\":38.76,\"revenue_shipping\":10.2,\"revenue_discount\":20.4,\"items\":3},\"idgoal=1\":{\"nb_conversions\":1,\"nb_visits_converted\":1,\"revenue\":0}},\"nb_conversions\":2,\"revenue\":232.56,\"segment\":\"campaignKeyword==more\"},{\"label\":\"learnmore\",\"nb_uniq_visitors\":1,\"nb_visits\":1,\"nb_actions\":\"1\",\"nb_users\":1,\"max_actions\":1,\"sum_visit_length\":\"0\",\"bounce_count\":\"1\",\"nb_visits_converted\":\"0\",\"segment\":\"campaignKeyword==learnmore\"},{\"label\":\"ordernow\",\"nb_uniq_visitors\":1,\"nb_visits\":1,\"nb_actions\":\"6\",\"nb_users\":0,\"max_actions\":6,\"sum_visit_length\":\"1\",\"bounce_count\":\"0\",\"nb_visits_converted\":\"0\",\"segment\":\"campaignKeyword==ordernow\"},{\"label\":\"www.facebook.com\",\"nb_uniq_visitors\":1,\"nb_visits\":1,\"nb_actions\":\"1\",\"nb_users\":1,\"max_actions\":1,\"sum_visit_length\":\"0\",\"bounce_count\":\"1\",\"nb_visits_converted\":\"0\",\"segment\":\"campaignKeyword==www.facebook.com\"}]", + "tsv": "label\tnb_uniq_visitors\tnb_visits\tnb_actions\tnb_users\tmax_actions\tsum_visit_length\tbounce_count\tnb_visits_converted\tgoals_idgoal=ecommerceOrder_nb_conversions\tgoals_idgoal=ecommerceOrder_nb_visits_converted\tgoals_idgoal=ecommerceOrder_revenue\tgoals_idgoal=ecommerceOrder_revenue_subtotal\tgoals_idgoal=ecommerceOrder_revenue_tax\tgoals_idgoal=ecommerceOrder_revenue_shipping\tgoals_idgoal=ecommerceOrder_revenue_discount\tgoals_idgoal=ecommerceOrder_items\tgoals_idgoal=1_nb_conversions\tgoals_idgoal=1_nb_visits_converted\tgoals_idgoal=1_revenue\tnb_conversions\trevenue\tmetadata_segment\nmore\t1\t2\t12\t1\t8\t5\t0\t2\t1\t1\t232.56\t204\t38.76\t10.2\t20.4\t3\t1\t1\t0\t2\t232.56\tcampaignKeyword==more\nlearnmore\t1\t1\t1\t1\t1\t0\t1\t0\t\t\t\t\t\t\t\t\t\t\t\t\t\tcampaignKeyword==learnmore\nordernow\t1\t1\t6\t0\t6\t1\t0\t0\t\t\t\t\t\t\t\t\t\t\t\t\t\tcampaignKeyword==ordernow\nwww.facebook.com\t1\t1\t1\t1\t1\t0\t1\t0\t\t\t\t\t\t\t\t\t\t\t\t\t\tcampaignKeyword==www.facebook.com" + }, + "MarketingCampaignsReporting.getSource": { + "xml": "", + "json": "", + "tsv": "" + }, + "MarketingCampaignsReporting.getMedium": { + "xml": "", + "json": "", + "tsv": "" + }, + "MarketingCampaignsReporting.getContent": { + "xml": "", + "json": "", + "tsv": "" + }, + "MarketingCampaignsReporting.getGroup": { + "xml": "", + "json": "", + "tsv": "" + }, + "MarketingCampaignsReporting.getPlacement": { + "xml": "", + "json": "", + "tsv": "" + }, + "MarketingCampaignsReporting.getSourceMedium": { + "xml": "", + "json": "", + "tsv": "" + }, + "LogViewer.getLogEntries": { + "xml": "{\"row\":[{\"severity\":\"\",\"tag\":\"\",\"datetime\":\"\",\"requestId\":\"\",\"message\":\"\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#17 {main} [Query: ?module=API&method=LogViewer.getLogConfig&format=Tsv&token_auth=removed&\\n convertToUnicode=0&hideIdSubDatable=1, CLI mode: 0]\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#16 \\/index.php(25): require_once('...')\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#15 \\/core\\/dispatch.php(33): Piwik\\\\FrontController->dispatch()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#14 \\/core\\/FrontController.php(170): Piwik\\\\FrontController->doDispatch()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#13 \\/core\\/FrontController.php(646): call_user_func_array()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#12 [internal function]: Piwik\\\\Plugins\\\\API\\\\Controller->index()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#11 \\/plugins\\/API\\/Controller.php(48): Piwik\\\\API\\\\Request->process()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#10 \\/core\\/API\\/Request.php(282): Piwik\\\\Context::executeWithQueryParameters()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#9 \\/core\\/Context.php(29): Piwik\\\\API\\\\Request->{closure:Piwik\\\\API\\\\Request::process():282}()\"}]}", + "json": "[{\"severity\":\"\",\"tag\":\"\",\"datetime\":\"\",\"requestId\":\"\",\"message\":\"\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#17 {main} [Query: ?module=API&method=LogViewer.getLogConfig&format=Tsv&token_auth=removed&convertToUnicode=0&hideIdSubDatable=1, CLI mode: 0]\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#16 \\/index.php(25): require_once('...')\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#15 \\/core\\/dispatch.php(33): Piwik\\\\FrontController->dispatch()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#14 \\/core\\/FrontController.php(170): Piwik\\\\FrontController->doDispatch()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#13 \\/core\\/FrontController.php(646): call_user_func_array()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#12 [internal function]: Piwik\\\\Plugins\\\\API\\\\Controller->index()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#11 \\/plugins\\/API\\/Controller.php(48): Piwik\\\\API\\\\Request->process()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#10 \\/core\\/API\\/Request.php(282): Piwik\\\\Context::executeWithQueryParameters()\"},{\"severity\":\"ERROR\",\"tag\":\"API\",\"datetime\":\"2025-09-19 07:55:22 UTC\",\"requestId\":\"1bc99\",\"message\":\"#9 \\/core\\/Context.php(29): Piwik\\\\API\\\\Request->{closure:Piwik\\\\API\\\\Request::process():282}()\"}]", + "tsv": "severity\ttag\tdatetime\trequestId\tmessage\n\t\t\t\t\nERROR\tAPI\t2025-09-19 08:04:51 UTC\t2904f\t\"#17 {main} [Query: ?module=API&method=LogViewer.getLogConfig&format=Tsv&token_auth=removed&convertToUnicode=0&hideIdSubDatable=1, CLI mode: 0]\"\nERROR\tAPI\t2025-09-19 08:04:51 UTC\t2904f\t#16 /index.php(25): require_once('...')\nERROR\tAPI\t2025-09-19 08:04:51 UTC\t2904f\t#15 /core/dispatch.php(33): Piwik\\FrontController->dispatch()\nERROR\tAPI\t2025-09-19 08:04:51 UTC\t2904f\t#14 /core/FrontController.php(170): Piwik\\FrontController->doDispatch()\nERROR\tAPI\t2025-09-19 08:04:51 UTC\t2904f\t#13 /core/FrontController.php(646): call_user_func_array()\nERROR\tAPI\t2025-09-19 08:04:51 UTC\t2904f\t#12 [internal function]: Piwik\\Plugins\\API\\Controller->index()\nERROR\tAPI\t2025-09-19 08:04:51 UTC\t2904f\t#11 /plugins/API/Controller.php(48): Piwik\\API\\Request->process()\nERROR\tAPI\t2025-09-19 08:04:51 UTC\t2904f\t#10 /core/API/Request.php(282): Piwik\\Context::executeWithQueryParameters()\nERROR\tAPI\t2025-09-19 08:04:51 UTC\t2904f\t#9 /core/Context.php(29): Piwik\\API\\Request->{closure:Piwik\\API\\Request::process():282}()" + }, + "LogViewer.getAvailableLogReaders": { + "xml": "{\"row\":[\"file\",\"database\"]}", + "json": "[\"file\",\"database\"]", + "tsv": "file\ndatabase" + }, + "LogViewer.getConfiguredLogReaders": { + "xml": "{\"row\":\"file\"}", + "json": "[\"file\"]", + "tsv": "file" + }, + "LogViewer.getLogConfig": { + "xml": "{\"log_writers\":{\"row\":[\"screen\",\"file\"]},\"log_level\":\"WARN\",\"logger_file_path\":\"tmp\\\/logs\\\/matomo.log\",\"logger_syslog_ident\":\"matomo\"}", + "json": "{\"log_writers\":[\"screen\",\"file\"],\"log_level\":\"WARN\",\"logger_file_path\":\"tmp\\\/logs\\\/matomo.log\",\"logger_syslog_ident\":\"matomo\"}", + "tsv": "" + }, + "CustomAlerts.getAlert": { + "xml": "{\"idalert\":\"1\",\"name\":\"Test Alert\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"greater_than\",\"metric_matched\":\"500\",\"compared_to\":\"7\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}}", + "json": "{\"idalert\":1,\"name\":\"Test Alert\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"greater_than\",\"metric_matched\":500,\"compared_to\":7,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]}", + "tsv": "" + }, + "CustomAlerts.getAlerts": { + "xml": "{\"row\":[{\"idalert\":\"1\",\"name\":\"Test Alert\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"greater_than\",\"metric_matched\":\"500\",\"compared_to\":\"7\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idalert\":\"2\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}}]}", + "json": "[{\"idalert\":1,\"name\":\"Test Alert\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"greater_than\",\"metric_matched\":500,\"compared_to\":7,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idalert\":2,\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]}]", + "tsv": "" + }, + "CustomAlerts.getTriggeredAlerts": { + "xml": "{\"row\":[{\"idtriggered\":\"1\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-13 01:55:48\",\"ts_last_sent\":\"2024-09-13 01:58:54\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"2\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-13 01:58:48\",\"ts_last_sent\":\"2024-09-13 01:58:54\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"3\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-13 02:00:39\",\"ts_last_sent\":\"2024-09-13 02:00:46\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"4\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-13 02:55:47\",\"ts_last_sent\":\"2024-09-13 02:55:53\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"5\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-16 23:33:41\",\"ts_last_sent\":\"\",\"value_old\":\"500.000\",\"value_new\":\"36.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"6\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-18 03:21:11\",\"ts_last_sent\":\"2024-09-18 03:23:05\",\"value_old\":\"36.000\",\"value_new\":\"1.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"9\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-18 22:18:55\",\"ts_last_sent\":\"2024-09-18 22:19:02\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"12\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-26 01:18:30\",\"ts_last_sent\":\"\",\"value_old\":\"2.000\",\"value_new\":\"1.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"13\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-10-11 02:26:50\",\"ts_last_sent\":\"\",\"value_old\":\"1190.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"14\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-11-28 20:34:14\",\"ts_last_sent\":\"\",\"value_old\":\"2.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"15\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-03-27 03:39:26\",\"ts_last_sent\":\"2025-03-27 03:39:26\",\"value_old\":\"4.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"16\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-04-03 20:59:07\",\"ts_last_sent\":\"2025-04-03 20:59:07\",\"value_old\":\"243.000\",\"value_new\":\"110.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"17\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-05-04 11:31:47\",\"ts_last_sent\":\"2025-05-04 11:31:47\",\"value_old\":\"330.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"18\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-05-10 11:31:23\",\"ts_last_sent\":\"2025-05-10 11:31:24\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"19\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-05-16 11:31:19\",\"ts_last_sent\":\"2025-05-16 11:31:19\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"20\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-05-18 11:31:24\",\"ts_last_sent\":\"2025-05-18 11:31:24\",\"value_old\":\"2.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"21\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-05-22 11:31:19\",\"ts_last_sent\":\"2025-05-22 11:31:19\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"22\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-05-25 11:31:24\",\"ts_last_sent\":\"2025-05-25 11:31:25\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"23\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-05-28 11:31:26\",\"ts_last_sent\":\"2025-05-28 11:31:27\",\"value_old\":\"304.000\",\"value_new\":\"3.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"24\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-05-29 11:31:25\",\"ts_last_sent\":\"2025-05-29 11:31:25\",\"value_old\":\"3.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"25\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-07-06 11:31:34\",\"ts_last_sent\":\"2025-07-06 11:31:34\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"26\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-07-16 11:31:35\",\"ts_last_sent\":\"2025-07-16 11:31:36\",\"value_old\":\"357.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"27\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-08-02 11:31:41\",\"ts_last_sent\":\"2025-08-02 11:31:41\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"28\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2025-08-09 11:31:44\",\"ts_last_sent\":\"2025-08-09 11:31:45\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}}]}", + "json": "[{\"idtriggered\":1,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-13 01:55:48\",\"ts_last_sent\":\"2024-09-13 01:58:54\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":2,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-13 01:58:48\",\"ts_last_sent\":\"2024-09-13 01:58:54\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":3,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-13 02:00:39\",\"ts_last_sent\":\"2024-09-13 02:00:46\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":4,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-13 02:55:47\",\"ts_last_sent\":\"2024-09-13 02:55:53\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":5,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-16 23:33:41\",\"ts_last_sent\":null,\"value_old\":\"500.000\",\"value_new\":\"36.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":6,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-18 03:21:11\",\"ts_last_sent\":\"2024-09-18 03:23:05\",\"value_old\":\"36.000\",\"value_new\":\"1.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":9,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-18 22:18:55\",\"ts_last_sent\":\"2024-09-18 22:19:02\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":12,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-26 01:18:30\",\"ts_last_sent\":null,\"value_old\":\"2.000\",\"value_new\":\"1.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":13,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-10-11 02:26:50\",\"ts_last_sent\":null,\"value_old\":\"1190.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":14,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-11-28 20:34:14\",\"ts_last_sent\":null,\"value_old\":\"2.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":15,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-03-27 03:39:26\",\"ts_last_sent\":\"2025-03-27 03:39:26\",\"value_old\":\"4.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":16,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-04-03 20:59:07\",\"ts_last_sent\":\"2025-04-03 20:59:07\",\"value_old\":\"243.000\",\"value_new\":\"110.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":17,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-05-04 11:31:47\",\"ts_last_sent\":\"2025-05-04 11:31:47\",\"value_old\":\"330.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":18,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-05-10 11:31:23\",\"ts_last_sent\":\"2025-05-10 11:31:24\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":19,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-05-16 11:31:19\",\"ts_last_sent\":\"2025-05-16 11:31:19\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":20,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-05-18 11:31:24\",\"ts_last_sent\":\"2025-05-18 11:31:24\",\"value_old\":\"2.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":21,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-05-22 11:31:19\",\"ts_last_sent\":\"2025-05-22 11:31:19\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":22,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-05-25 11:31:24\",\"ts_last_sent\":\"2025-05-25 11:31:25\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":23,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-05-28 11:31:26\",\"ts_last_sent\":\"2025-05-28 11:31:27\",\"value_old\":\"304.000\",\"value_new\":\"3.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":24,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-05-29 11:31:25\",\"ts_last_sent\":\"2025-05-29 11:31:25\",\"value_old\":\"3.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":25,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-07-06 11:31:34\",\"ts_last_sent\":\"2025-07-06 11:31:34\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":26,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-07-16 11:31:35\",\"ts_last_sent\":\"2025-07-16 11:31:36\",\"value_old\":\"357.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":27,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2025-08-02 11:31:41\",\"ts_last_sent\":\"2025-08-02 11:31:41\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]}]", + "tsv": "" + }, + "CustomDimensions.getConfiguredCustomDimensions": { + "xml": "{\"row\":[{\"idcustomdimension\":\"1\",\"idsite\":\"1\",\"name\":\"User Type\",\"index\":\"1\",\"scope\":\"visit\",\"active\":\"1\",\"extractions\":\"\",\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"2\",\"idsite\":\"1\",\"name\":\"Page Author\",\"index\":\"1\",\"scope\":\"action\",\"active\":\"1\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"3\",\"idsite\":\"1\",\"name\":\"Page Age\",\"index\":\"2\",\"scope\":\"action\",\"active\":\"0\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"4\",\"idsite\":\"1\",\"name\":\"Page Location\",\"index\":\"3\",\"scope\":\"action\",\"active\":\"1\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"5\",\"idsite\":\"1\",\"name\":\"Page Type\",\"index\":\"4\",\"scope\":\"action\",\"active\":\"1\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"},{\"idcustomdimension\":\"6\",\"idsite\":\"1\",\"name\":\"Diving Rating\",\"index\":\"5\",\"scope\":\"action\",\"active\":\"0\",\"extractions\":{\"row\":{\"dimension\":\"url\",\"pattern\":\"\"}},\"case_sensitive\":\"1\"}]}", + "json": "[{\"idcustomdimension\":\"1\",\"idsite\":\"1\",\"name\":\"User Type\",\"index\":\"1\",\"scope\":\"visit\",\"active\":true,\"extractions\":[],\"case_sensitive\":true},{\"idcustomdimension\":\"2\",\"idsite\":\"1\",\"name\":\"Page Author\",\"index\":\"1\",\"scope\":\"action\",\"active\":true,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true},{\"idcustomdimension\":\"3\",\"idsite\":\"1\",\"name\":\"Page Age\",\"index\":\"2\",\"scope\":\"action\",\"active\":false,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true},{\"idcustomdimension\":\"4\",\"idsite\":\"1\",\"name\":\"Page Location\",\"index\":\"3\",\"scope\":\"action\",\"active\":true,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true},{\"idcustomdimension\":\"5\",\"idsite\":\"1\",\"name\":\"Page Type\",\"index\":\"4\",\"scope\":\"action\",\"active\":true,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true},{\"idcustomdimension\":\"6\",\"idsite\":\"1\",\"name\":\"Diving Rating\",\"index\":\"5\",\"scope\":\"action\",\"active\":false,\"extractions\":[{\"dimension\":\"url\",\"pattern\":\"\"}],\"case_sensitive\":true}]", + "tsv": "" + }, + "CustomDimensions.getAvailableExtractionDimensions": { + "xml": "{\"row\":[{\"value\":\"url\",\"name\":\"Page URL\"},{\"value\":\"urlparam\",\"name\":\"Page URL Parameter\"},{\"value\":\"action_name\",\"name\":\"Page Title\"}]}", + "json": "[{\"value\":\"url\",\"name\":\"Page URL\"},{\"value\":\"urlparam\",\"name\":\"Page URL Parameter\"},{\"value\":\"action_name\",\"name\":\"Page Title\"}]", + "tsv": "value\tname\nurl\tPage URL\nurlparam\tPage URL Parameter\naction_name\tPage Title" + } +} \ No newline at end of file diff --git a/tests/Resources/ExampleResponsesNormalised/ExamplesPostTruncationByType.json b/tests/Resources/ExampleResponsesNormalised/ExamplesPostTruncationByType.json new file mode 100644 index 0000000..27a0f7a --- /dev/null +++ b/tests/Resources/ExampleResponsesNormalised/ExamplesPostTruncationByType.json @@ -0,0 +1,6 @@ +{ + "CustomAlerts.getTriggeredAlerts": { + "xml": "{\"row\":[{\"idtriggered\":\"1\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-13 01:55:48\",\"ts_last_sent\":\"2024-09-13 01:58:54\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"2\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-13 01:58:48\",\"ts_last_sent\":\"2024-09-13 01:58:54\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"3\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-13 02:00:39\",\"ts_last_sent\":\"2024-09-13 02:00:46\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"4\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-13 02:55:47\",\"ts_last_sent\":\"2024-09-13 02:55:53\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}},{\"idtriggered\":\"5\",\"idalert\":\"2\",\"idsite\":\"1\",\"ts_triggered\":\"2024-09-16 23:33:41\",\"ts_last_sent\":\"\",\"value_old\":\"500.000\",\"value_new\":\"36.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":\"\",\"report_matched\":\"\",\"report_mediums\":{\"row\":\"email\"},\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":\"20\",\"compared_to\":\"1\",\"email_me\":\"1\",\"additional_emails\":\"\",\"phone_numbers\":\"\",\"slack_channel_id\":\"\",\"id_sites\":{\"row\":\"1\"}}]}", + "json": "[{\"idtriggered\":1,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-13 01:55:48\",\"ts_last_sent\":\"2024-09-13 01:58:54\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":2,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-13 01:58:48\",\"ts_last_sent\":\"2024-09-13 01:58:54\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":3,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-13 02:00:39\",\"ts_last_sent\":\"2024-09-13 02:00:46\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":4,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-13 02:55:47\",\"ts_last_sent\":\"2024-09-13 02:55:53\",\"value_old\":\"1.000\",\"value_new\":\"0.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]},{\"idtriggered\":5,\"idalert\":2,\"idsite\":1,\"ts_triggered\":\"2024-09-16 23:33:41\",\"ts_last_sent\":null,\"value_old\":\"500.000\",\"value_new\":\"36.000\",\"name\":\"Test Visit Drop Previous Day\",\"login\":\"someUserName\",\"period\":\"day\",\"report\":\"VisitsSummary_get\",\"report_condition\":null,\"report_matched\":null,\"report_mediums\":[\"email\"],\"metric\":\"nb_uniq_visitors\",\"metric_condition\":\"percentage_decrease_more_than\",\"metric_matched\":20,\"compared_to\":1,\"email_me\":1,\"additional_emails\":[],\"phone_numbers\":[],\"slack_channel_id\":null,\"id_sites\":[1]}]" + } +} \ No newline at end of file diff --git a/tests/Resources/ExampleResponsesNormalised/ExamplesSchemasByType.json b/tests/Resources/ExampleResponsesNormalised/ExamplesSchemasByType.json new file mode 100644 index 0000000..9ebf4fe --- /dev/null +++ b/tests/Resources/ExampleResponsesNormalised/ExamplesSchemasByType.json @@ -0,0 +1,86 @@ +{ + "MarketingCampaignsReporting.getId": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "MarketingCampaignsReporting.getName": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\",{\"@OA\\\\Property\":[\"property=\\\"goals\\\",\",\"type=\\\"object\\\",\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\"]}}]}]}}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"additionalProperties=true,\",\"@OA\\\\Property\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"@OA\\\\Property(property=\\\"label\\\", type=\\\"string\\\")\",\"2\":\"@OA\\\\Property(property=\\\"nb_uniq_visitors\\\", type=\\\"integer\\\")\",\"3\":\"@OA\\\\Property(property=\\\"nb_visits\\\", type=\\\"integer\\\")\",\"4\":\"@OA\\\\Property(property=\\\"nb_actions\\\", type=\\\"integer\\\")\",\"5\":\"@OA\\\\Property(property=\\\"nb_users\\\", type=\\\"integer\\\")\",\"6\":\"@OA\\\\Property(property=\\\"max_actions\\\", type=\\\"integer\\\")\",\"7\":\"@OA\\\\Property(property=\\\"sum_visit_length\\\", type=\\\"integer\\\")\",\"8\":\"@OA\\\\Property(property=\\\"bounce_count\\\", type=\\\"integer\\\")\",\"9\":\"@OA\\\\Property(property=\\\"nb_visits_converted\\\", type=\\\"integer\\\")\",\"@OA\\\\Property\":{\"0\":\"property=\\\"goals\\\",\",\"1\":\"type=\\\"object\\\",\",\"@OA\\\\Property\":[\"property=\\\"idgoal=1\\\",\",\"type=\\\"object\\\",\",\"@OA\\\\Property(property=\\\"nb_conversions\\\", type=\\\"integer\\\")\",\"@OA\\\\Property(property=\\\"nb_visits_converted\\\", type=\\\"integer\\\")\",\"@OA\\\\Property(property=\\\"revenue\\\", type=\\\"integer\\\")\"]},\"10\":\"@OA\\\\Property(property=\\\"nb_conversions\\\", type=\\\"integer\\\")\",\"11\":\"@OA\\\\Property(property=\\\"revenue\\\", type={\\\"string\\\", \\\"number\\\", \\\"integer\\\", \\\"boolean\\\", \\\"array\\\", \\\"object\\\", \\\"null\\\"})\",\"12\":\"@OA\\\\Property(property=\\\"segment\\\", type=\\\"string\\\")\"}}}}" + }, + "MarketingCampaignsReporting.getKeyword": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\",{\"@OA\\\\Property\":[\"property=\\\"goals\\\",\",\"type=\\\"object\\\",\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\"]}}]}]}}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"additionalProperties=true,\",\"@OA\\\\Property\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"@OA\\\\Property(property=\\\"label\\\", type=\\\"string\\\")\",\"2\":\"@OA\\\\Property(property=\\\"nb_uniq_visitors\\\", type=\\\"integer\\\")\",\"3\":\"@OA\\\\Property(property=\\\"nb_visits\\\", type=\\\"integer\\\")\",\"4\":\"@OA\\\\Property(property=\\\"nb_actions\\\", type=\\\"string\\\")\",\"5\":\"@OA\\\\Property(property=\\\"nb_users\\\", type=\\\"integer\\\")\",\"6\":\"@OA\\\\Property(property=\\\"max_actions\\\", type=\\\"integer\\\")\",\"7\":\"@OA\\\\Property(property=\\\"sum_visit_length\\\", type=\\\"string\\\")\",\"8\":\"@OA\\\\Property(property=\\\"bounce_count\\\", type=\\\"string\\\")\",\"9\":\"@OA\\\\Property(property=\\\"nb_visits_converted\\\", type=\\\"string\\\")\",\"@OA\\\\Property\":{\"0\":\"property=\\\"goals\\\",\",\"1\":\"type=\\\"object\\\",\",\"@OA\\\\Property\":[\"property=\\\"idgoal=1\\\",\",\"type=\\\"object\\\",\",\"@OA\\\\Property(property=\\\"nb_conversions\\\", type=\\\"integer\\\")\",\"@OA\\\\Property(property=\\\"nb_visits_converted\\\", type=\\\"integer\\\")\",\"@OA\\\\Property(property=\\\"revenue\\\", type=\\\"integer\\\")\"]},\"10\":\"@OA\\\\Property(property=\\\"nb_conversions\\\", type=\\\"integer\\\")\",\"11\":\"@OA\\\\Property(property=\\\"revenue\\\", type={\\\"string\\\", \\\"number\\\", \\\"integer\\\", \\\"boolean\\\", \\\"array\\\", \\\"object\\\", \\\"null\\\"})\",\"12\":\"@OA\\\\Property(property=\\\"segment\\\", type=\\\"string\\\")\"}}}}" + }, + "MarketingCampaignsReporting.getSource": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "MarketingCampaignsReporting.getMedium": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "MarketingCampaignsReporting.getContent": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "MarketingCampaignsReporting.getGroup": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "MarketingCampaignsReporting.getPlacement": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "MarketingCampaignsReporting.getSourceMedium": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "LogViewer.getLogEntries": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\"]}}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"additionalProperties=true,\",\"@OA\\\\Property\":[\"type=\\\"object\\\",\",\"@OA\\\\Property(property=\\\"severity\\\", type=\\\"string\\\")\",\"@OA\\\\Property(property=\\\"tag\\\", type=\\\"string\\\")\",\"@OA\\\\Property(property=\\\"datetime\\\", type=\\\"string\\\")\",\"@OA\\\\Property(property=\\\"requestId\\\", type=\\\"string\\\")\",\"@OA\\\\Property(property=\\\"message\\\", type=\\\"string\\\")\"]}}}" + }, + "LogViewer.getAvailableLogReaders": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"string\\\"\"]}}]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "LogViewer.getConfiguredLogReaders": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "LogViewer.getLogConfig": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":[\"property=\\\"log_writers\\\",\",\"type=\\\"object\\\",\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"string\\\"\"]}}]}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"object\\\",\",\"@OA\\\\Property\":[\"property=\\\"log_writers\\\",\",\"type=\\\"array\\\",\",\"@OA\\\\Items()\"],\"1\":\"@OA\\\\Property(property=\\\"log_level\\\", type=\\\"string\\\")\",\"2\":\"@OA\\\\Property(property=\\\"logger_file_path\\\", type=\\\"string\\\")\",\"3\":\"@OA\\\\Property(property=\\\"logger_syslog_ident\\\", type=\\\"string\\\")\"}}" + }, + "CustomAlerts.getAlert": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "CustomAlerts.getAlerts": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\",{\"@OA\\\\Property\":[\"property=\\\"report_mediums\\\",\",\"type=\\\"object\\\",\"]},{\"@OA\\\\Property\":[\"property=\\\"id_sites\\\",\",\"type=\\\"object\\\",\"]}]}}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"additionalProperties=true,\",\"@OA\\\\Property\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"@OA\\\\Property(property=\\\"idalert\\\", type=\\\"integer\\\")\",\"2\":\"@OA\\\\Property(property=\\\"name\\\", type=\\\"string\\\")\",\"3\":\"@OA\\\\Property(property=\\\"login\\\", type=\\\"string\\\")\",\"4\":\"@OA\\\\Property(property=\\\"period\\\", type=\\\"string\\\")\",\"5\":\"@OA\\\\Property(property=\\\"report\\\", type=\\\"string\\\")\",\"6\":\"@OA\\\\Property(property=\\\"report_condition\\\", type={\\\"string\\\", \\\"number\\\", \\\"integer\\\", \\\"boolean\\\", \\\"array\\\", \\\"object\\\", \\\"null\\\"})\",\"7\":\"@OA\\\\Property(property=\\\"report_matched\\\", type={\\\"string\\\", \\\"number\\\", \\\"integer\\\", \\\"boolean\\\", \\\"array\\\", \\\"object\\\", \\\"null\\\"})\",\"@OA\\\\Property\":[\"property=\\\"id_sites\\\",\",\"type=\\\"array\\\",\",\"@OA\\\\Items()\"],\"8\":\"@OA\\\\Property(property=\\\"metric\\\", type=\\\"string\\\")\",\"9\":\"@OA\\\\Property(property=\\\"metric_condition\\\", type=\\\"string\\\")\",\"10\":\"@OA\\\\Property(property=\\\"metric_matched\\\", type=\\\"integer\\\")\",\"11\":\"@OA\\\\Property(property=\\\"compared_to\\\", type=\\\"integer\\\")\",\"12\":\"@OA\\\\Property(property=\\\"email_me\\\", type=\\\"integer\\\")\",\"13\":\"@OA\\\\Property(property=\\\"slack_channel_id\\\", type={\\\"string\\\", \\\"number\\\", \\\"integer\\\", \\\"boolean\\\", \\\"array\\\", \\\"object\\\", \\\"null\\\"})\"}}}}" + }, + "CustomAlerts.deleteAlert": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\"]}", + "json": "{\"@OA\\\\Schema\":[\"type=\\\"array\\\",\",\"@OA\\\\Items()\"]}" + }, + "CustomAlerts.getTriggeredAlerts": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\",{\"@OA\\\\Property\":[\"property=\\\"report_mediums\\\",\",\"type=\\\"object\\\",\"]},{\"@OA\\\\Property\":[\"property=\\\"id_sites\\\",\",\"type=\\\"object\\\",\"]}]}}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"additionalProperties=true,\",\"@OA\\\\Property\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"@OA\\\\Property(property=\\\"idtriggered\\\", type=\\\"integer\\\")\",\"2\":\"@OA\\\\Property(property=\\\"idalert\\\", type=\\\"integer\\\")\",\"3\":\"@OA\\\\Property(property=\\\"idsite\\\", type=\\\"integer\\\")\",\"4\":\"@OA\\\\Property(property=\\\"ts_triggered\\\", type=\\\"string\\\")\",\"5\":\"@OA\\\\Property(property=\\\"ts_last_sent\\\", type=\\\"string\\\")\",\"6\":\"@OA\\\\Property(property=\\\"value_old\\\", type=\\\"string\\\")\",\"7\":\"@OA\\\\Property(property=\\\"value_new\\\", type=\\\"string\\\")\",\"8\":\"@OA\\\\Property(property=\\\"name\\\", type=\\\"string\\\")\",\"9\":\"@OA\\\\Property(property=\\\"login\\\", type=\\\"string\\\")\",\"10\":\"@OA\\\\Property(property=\\\"period\\\", type=\\\"string\\\")\",\"11\":\"@OA\\\\Property(property=\\\"report\\\", type=\\\"string\\\")\",\"12\":\"@OA\\\\Property(property=\\\"report_condition\\\", type={\\\"string\\\", \\\"number\\\", \\\"integer\\\", \\\"boolean\\\", \\\"array\\\", \\\"object\\\", \\\"null\\\"})\",\"13\":\"@OA\\\\Property(property=\\\"report_matched\\\", type={\\\"string\\\", \\\"number\\\", \\\"integer\\\", \\\"boolean\\\", \\\"array\\\", \\\"object\\\", \\\"null\\\"})\",\"@OA\\\\Property\":[\"property=\\\"id_sites\\\",\",\"type=\\\"array\\\",\",\"@OA\\\\Items()\"],\"14\":\"@OA\\\\Property(property=\\\"metric\\\", type=\\\"string\\\")\",\"15\":\"@OA\\\\Property(property=\\\"metric_condition\\\", type=\\\"string\\\")\",\"16\":\"@OA\\\\Property(property=\\\"metric_matched\\\", type=\\\"integer\\\")\",\"17\":\"@OA\\\\Property(property=\\\"compared_to\\\", type=\\\"integer\\\")\",\"18\":\"@OA\\\\Property(property=\\\"email_me\\\", type=\\\"integer\\\")\",\"19\":\"@OA\\\\Property(property=\\\"slack_channel_id\\\", type={\\\"string\\\", \\\"number\\\", \\\"integer\\\", \\\"boolean\\\", \\\"array\\\", \\\"object\\\", \\\"null\\\"})\"}}}}" + }, + "CustomDimensions.getCustomDimension": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"string\\\"\"]}}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"additionalProperties=true,\",\"@OA\\\\Property\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"@OA\\\\Property(property=\\\"label\\\", type=\\\"string\\\")\",\"2\":\"@OA\\\\Property(property=\\\"nb_uniq_visitors\\\", type=\\\"string\\\")\",\"3\":\"@OA\\\\Property(property=\\\"nb_visits\\\", type=\\\"string\\\")\",\"4\":\"@OA\\\\Property(property=\\\"nb_actions\\\", type=\\\"string\\\")\",\"5\":\"@OA\\\\Property(property=\\\"max_actions\\\", type=\\\"integer\\\")\",\"6\":\"@OA\\\\Property(property=\\\"sum_visit_length\\\", type=\\\"string\\\")\",\"7\":\"@OA\\\\Property(property=\\\"bounce_count\\\", type=\\\"string\\\")\",\"8\":\"@OA\\\\Property(property=\\\"nb_visits_converted\\\", type=\\\"string\\\")\",\"@OA\\\\Property\":{\"0\":\"property=\\\"goals\\\",\",\"1\":\"type=\\\"object\\\",\",\"@OA\\\\Property\":[\"property=\\\"idgoal=8\\\",\",\"type=\\\"object\\\",\",\"@OA\\\\Property(property=\\\"nb_conversions\\\", type=\\\"integer\\\")\",\"@OA\\\\Property(property=\\\"nb_visits_converted\\\", type=\\\"integer\\\")\",\"@OA\\\\Property(property=\\\"revenue\\\", type=\\\"integer\\\")\"]},\"9\":\"@OA\\\\Property(property=\\\"nb_conversions\\\", type=\\\"integer\\\")\",\"10\":\"@OA\\\\Property(property=\\\"revenue\\\", type=\\\"integer\\\")\",\"11\":\"@OA\\\\Property(property=\\\"avg_time_on_site\\\", type=\\\"integer\\\")\",\"12\":\"@OA\\\\Property(property=\\\"bounce_rate\\\", type=\\\"string\\\")\",\"13\":\"@OA\\\\Property(property=\\\"nb_actions_per_visit\\\", type={\\\"string\\\", \\\"number\\\", \\\"integer\\\", \\\"boolean\\\", \\\"array\\\", \\\"object\\\", \\\"null\\\"})\",\"14\":\"@OA\\\\Property(property=\\\"segment\\\", type=\\\"string\\\")\"}}}}" + }, + "CustomDimensions.getConfiguredCustomDimensions": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\"]}}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"additionalProperties=true,\",\"@OA\\\\Property\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"@OA\\\\Property(property=\\\"idcustomdimension\\\", type=\\\"string\\\")\",\"2\":\"@OA\\\\Property(property=\\\"idsite\\\", type=\\\"string\\\")\",\"3\":\"@OA\\\\Property(property=\\\"name\\\", type=\\\"string\\\")\",\"4\":\"@OA\\\\Property(property=\\\"index\\\", type=\\\"string\\\")\",\"5\":\"@OA\\\\Property(property=\\\"scope\\\", type=\\\"string\\\")\",\"6\":\"@OA\\\\Property(property=\\\"active\\\", type=\\\"boolean\\\")\",\"@OA\\\\Property\":[\"property=\\\"extractions\\\",\",\"type=\\\"array\\\",\",\"@OA\\\\Items()\"],\"7\":\"@OA\\\\Property(property=\\\"case_sensitive\\\", type=\\\"boolean\\\")\"}}}}" + }, + "CustomDimensions.getAvailableScopes": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\"]}}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"additionalProperties=true,\",\"@OA\\\\Property\":[\"type=\\\"object\\\",\",\"@OA\\\\Property(property=\\\"value\\\", type=\\\"string\\\")\",\"@OA\\\\Property(property=\\\"name\\\", type=\\\"string\\\")\",\"@OA\\\\Property(property=\\\"numSlotsAvailable\\\", type=\\\"integer\\\")\",\"@OA\\\\Property(property=\\\"numSlotsUsed\\\", type=\\\"integer\\\")\",\"@OA\\\\Property(property=\\\"numSlotsLeft\\\", type=\\\"integer\\\")\",\"@OA\\\\Property(property=\\\"supportsExtractions\\\", type=\\\"boolean\\\")\"]}}}" + }, + "CustomDimensions.getAvailableExtractionDimensions": { + "xml": "{\"@OA\\\\Schema\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"result\\\"),\",{\"@OA\\\\Property\":{\"0\":\"property=\\\"row\\\",\",\"1\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":[\"type=\\\"object\\\",\",\"@OA\\\\Xml(name=\\\"row\\\"),\",\"additionalProperties=true,\"]}}]}", + "json": "{\"@OA\\\\Schema\":{\"0\":\"type=\\\"array\\\",\",\"@OA\\\\Items\":{\"0\":\"type=\\\"object\\\",\",\"1\":\"additionalProperties=true,\",\"@OA\\\\Property\":[\"type=\\\"object\\\",\",\"@OA\\\\Property(property=\\\"value\\\", type=\\\"string\\\")\",\"@OA\\\\Property(property=\\\"name\\\", type=\\\"string\\\")\"]}}}" + } +} diff --git a/tests/Resources/MockAnnotationGenerator.php b/tests/Resources/MockAnnotationGenerator.php new file mode 100644 index 0000000..b7e376c --- /dev/null +++ b/tests/Resources/MockAnnotationGenerator.php @@ -0,0 +1,83 @@ + 'url', 'pattern' => 'index_(.+).html'), array('dimension' => 'urlparam', 'pattern' => '...')) + * Supported dimensions are eg 'url', 'urlparam' and 'action_name'. To get an up to date list of + * supported dimensions request the API method `CustomDimensions.getAvailableExtractionDimensions`. + * Note: Extractions can be only set for dimensions in scope 'action'. + * @param int|bool $caseSensitive '0' if extractions should be applied case insensitive, '1' if extractions should be applied case sensitive + * @return int Returns the ID of the configured dimension. Note that the same idDimension will be used for different websites. + * @throws \Exception + */ + public function configureNewCustomDimension($idSite, $name, $scope, $active, $extractions = array(), $caseSensitive = true) + { + return 1; + } + + /** + * Updates an existing Custom Dimension. This method updates all values, you need to pass existing values of the + * dimension if you do not want to reset any value. Requires at least Admin access for the specified website. + * + * @param int $idDimension The id of a Custom Dimension. + * @param int $idSite The idSite the dimension belongs to + * @param string $name The name of the dimension + * @param int $active '0' if dimension should be inactive, '1' if dimension should be active + * @param array $extractions Either an empty array or if extractions shall be used one or multiple extractions + * the format array(array('dimension' => 'url', 'pattern' => 'index_(.+).html'), array('dimension' => 'urlparam', 'pattern' => '...')) + * Supported dimensions are eg 'url', 'urlparam' and 'action_name'. To get an up to date list of + * supported dimensions request the API method `CustomDimensions.getAvailableExtractionDimensions`. + * Note: Extractions can be only set for dimensions in scope 'action'. + * @param int|bool|null $caseSensitive '0' if extractions should be applied case insensitive, '1' if extractions should be applied case sensitive, null to keep case sensitive unchanged + * @throws \Exception + */ + public function configureExistingCustomDimension($idDimension, $idSite, $name, $active, $extractions = array(), $caseSensitive = null) + { + } + + /** + * Get a list of all configured CustomDimensions for a given website. Requires at least Admin access for the + * specified website. + * + * @param int $idSite + * @return array + */ + public function getConfiguredCustomDimensions($idSite) + { + return []; + } + + /** + * For convenience. Hidden to reduce API surface area. + * @hide + */ + public function getConfiguredCustomDimensionsHavingScope($idSite, $scope) + { + } + + /** + * Get a list of all supported scopes that can be used in the API method + * `CustomDimensions.configureNewCustomDimension`. The response also contains information whether more Custom + * Dimensions can be created or not. Requires at least Admin access for the specified website. + * + * @param int $idSite + * @return array + */ + public function getAvailableScopes($idSite) + { + return []; + } + + /** + * Get a list of all available dimensions that can be used in an extraction. Requires at least Admin access + * to one website. + * + * @return array + */ + public function getAvailableExtractionDimensions() + { + return []; + } + + /** + * Copies a specified custom report to one or more sites. If a custom report with the same name already exists, the new custom report + * will have an automatically adjusted name to make it unique to the assigned site. + * + * @param int $idSite + * @param int $idCustomReport ID of the custom report to duplicate. + * @param int[] $idDestinationSites Optional array of IDs identifying which site(s) the new custom report is to be + * assigned to. The default is [idSite] when nothing is provided. + * @return array + * @throws \Exception + */ + public function duplicateCustomReport(int $idSite, int $idCustomReport, array $idDestinationSites = []): array + { + return []; + } + + /** + * Adds a new custom report + * @param int $idSite + * @param string $name The name of the report. + * @param string $reportType The type of report you want to create, for example 'table' or 'evolution'. + * For a list of available reports call 'CustomReports.getAvailableReportTypes' + * @param string[] $metricIds A list of metric IDs. For a list of available metrics call 'CustomReports.getAvailableMetrics' + * @param string $categoryId By default, the report will be put into a custom report category unless a specific + * categoryId is provided. For a list of available categories call 'CustomReports.getAvailableCategories'. + * @param string[] $dimensionIds A list of dimension IDs. For a list of available metrics call 'CustomReports.getAvailableDimensions' + * @param bool|string $subcategoryId By default, a new reporting page will be created for this report unless you + * specifiy a specific name or subcategoryID. For a list of available subcategories + * call 'CustomReports.getAvailableCategories'. + * @param string $description An optional description for the report, will be shown in the title help icon of the report. + * @param string $segmentFilter An optional segment to filter the report data. Needs to be sent urlencoded. + * @param string[] $multipleIdSites An optional list of idsites for which we need to execute the report + * @return int + */ + public function addCustomReport($idSite, $name, $reportType, $metricIds, $categoryId = false, $dimensionIds = array(), $subcategoryId = false, $description = '', $segmentFilter = '', $multipleIdSites = []) + { + return 4; + } + + /** + * Updates an existing custom report. Be aware that if you change metrics, dimensions, the report type or the segment filter, + * previously processed/archived reports may become unavailable and would need to be re-processed. + * + * @param int $idSite + * @param int $idCustomReport + * @param string $name The name of the report. + * @param string $reportType The type of report you want to create, for example 'table' or 'evolution'. + * For a list of available reports call 'CustomReports.getAvailableReportTypes' + * @param string[] $metricIds A list of metric IDs. For a list of available metrics call 'CustomReports.getAvailableMetrics' + * @param string $categoryId By default, the report will be put into a custom report category unless a specific + * categoryId is provided. For a list of available categories call 'CustomReports.getAvailableCategories'. + * @param string[] $dimensionIds A list of dimension IDs. For a list of available metrics call 'CustomReports.getAvailableDimensions' + * @param bool|string $subcategoryId By default, a new reporting page will be created for this report unless you + * specify a specific name or subcategoryID. For a list of available subcategories + * call 'CustomReports.getAvailableCategories'. + * @param string $description An optional description for the report, will be shown in the title help icon of the report. + * @param string $segmentFilter An optional segment to filter the report data. Needs to be sent urlencoded. + * @param int[] $subCategoryReportIds List of sub report ids mapped to this report + * @param string[] $multipleIdSites An optional list of idSites for which we need to execute the report + */ + public function updateCustomReport( + $idSite, + $idCustomReport, + $name, + $reportType, + $metricIds, + $categoryId = false, + $dimensionIds = [], + $subcategoryId = false, + $description = '', + $segmentFilter = '', + $subCategoryReportIds = [], + $multipleIdSites = [] + ): void { + } + + /** + * Get all custom report configurations for a specific site. + * + * @param int $idSite + * @param bool $skipCategoryMetadata + * @return array + */ + public function getConfiguredReports($idSite, $skipCategoryMetadata = false) + { + return []; + } + + /** + * Get a specific custom report configuration. + * + * @param int $idSite + * @param int $idCustomReport The ID of the custom report. [@example=1] + * @return array + */ + public function getConfiguredReport($idSite, $idCustomReport) + { + return []; + } + + /** + * Deletes the given custom report. + * + * When a custom report is deleted, its report will be no longer available in the API and tracked data for this + * report might be removed at some point by the system. + * + * @param int $idSite + * @param int $idForm + */ + public function deleteCustomReport($idSite, $idCustomReport): void + { + } + + /** + * Pauses the given custom report. + * + * When a custom report is paused, its report will be no longer be archived + * + * @param int $idSite + * @param int $idCustomReport + */ + public function pauseCustomReport($idSite, $idCustomReport): void + { + } + + /** + * Resumes the given custom report. + * + * When a custom report is resumed, its report will start archiving again + * + * @param int $idSite + * @param int $idCustomReport + */ + public function resumeCustomReport($idSite, $idCustomReport): void + { + } + + /** + * Get a list of available categories that can be used in custom reports. + * + * @param int $idSite + * @return array + */ + public function getAvailableCategories($idSite) + { + return []; + } + + /** + * Get a list of available report types that can be used in custom reports. + * + * @return array + */ + public function getAvailableReportTypes() + { + return []; + } + + /** + * Get a list of available dimensions that can be used in custom reports. + * + * @param int $idSite + * @return array + */ + public function getAvailableDimensions($idSite) + { + return []; + } + + /** + * Get a list of available metrics that can be used in custom reports. + * + * @param int $idSite + * @return array + */ + public function getAvailableMetrics($idSite) + { + return []; + } + + /** + * Get report data for a previously created custom report. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $idCustomReport + * @param bool|string $segment + * @param bool $expanded + * @param bool $flat + * @param int|bool $idSubtable + * @param string|bool $columns + * @return DataTable\DataTableInterface + */ + public function getCustomReport($idSite, $period, $date, $idCustomReport, $segment = false, $expanded = false, $flat = false, $idSubtable = false, $columns = false) + { + return new DataTable(); + } + + /** + * Get summary metrics for a specific funnel like the number of conversions, the conversion rate, the number of + * entries etc. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $idFunnel Either idFunnel or idGoal has to be set + * @param int $idGoal Either idFunnel or idGoal has to be set. If goal given, will return the latest funnel for that goal. [@example=4] + * @param string $segment + * + * @return DataTable|DataTable\Map + */ + public function getMetrics($idSite, $period, $date, $idFunnel = false, $idGoal = false, $segment = false) + { + return new DataTable(); + } + + /** + * Get funnel flow information. The returned datatable will include a row for each step within the funnel + * showing information like how many visits have entered or left the funnel at a certain position, how many + * have completed a certain step etc. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $idFunnel Either idFunnel or idGoal has to be set + * @param int $idGoal Either idFunnel or idGoal has to be set. If goal given, will return the latest funnel for that goal. [@example=4] + * @param string $segment + * + * @return DataTable + * @throws \Exception + */ + public function getFunnelFlow($idSite, $period, $date, $idFunnel = false, $idGoal = false, $segment = false) + { + return new DataTable(); + } + + /** + * Get funnel flow information. The returned datatable will include a row for each step within the funnel + * showing information like how many visits have entered or left the funnel at a certain position, how many + * have completed a certain step etc. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $idFunnel Either idFunnel or idGoal has to be set + * @param int $idGoal Either idFunnel or idGoal has to be set. If goal given, will return the latest funnel for that goal. [@example=4] + * @param string $segment + * + * @return DataTable + * @throws \Exception + */ + public function getFunnelFlowTable($idSite, $period, $date, $idFunnel = false, $idGoal = false, $segment = false) + { + return new DataTable(); + } + + /** + * Get subTable funnel flow information. The returned datatable will include a row for proceeded, entries, and + * exists. If they have any values, they'll have a subTable of their own. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $stepPosition The step number to pull the data for. [@example=1] + * @param int $idFunnel Either idFunnel or idGoal has to be set + * @param int $idGoal Either idFunnel or idGoal has to be set. If goal given, will return the latest funnel for that goal. [@example=4] + * @param string $segment + * + * @return DataTable + * @throws \Exception + */ + public function getFunnelStepSubtable($idSite, $period, $date, $stepPosition, $idFunnel = false, $idGoal = false, $segment = false) + { + return new DataTable(); + } + + /** + * Get all entry actions for the given funnel at the given step. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $idFunnel The ID of the funnel for which to get data. [@example=99] + * @param string $segment + * @param string $step + * @param bool $expanded + * @param int|string $idSubtable + * @param bool $flat + * + * @return DataTable + */ + public function getFunnelEntries($idSite, $period, $date, $idFunnel, $segment = false, $step = false, $expanded = false, $idSubtable = false, $flat = false) + { + return new DataTable(); + } + + /** + * Get all exit actions for the given funnel at the given step. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $idFunnel The ID of the funnel for which to get data. [@example=99] + * @param string $segment + * @param string $step + * + * @return DataTable + */ + public function getFunnelExits($idSite, $period, $date, $idFunnel, $segment = false, $step = false) + { + return new DataTable(); + } + + /** + * Get funnel information for this goal. + * + * @param int $idSite + * @param int $idGoal The ID of the goal for which to get funnel data. [@example=4] + * + * @return array|null Null when no funnel has been configured yet, the funnel otherwise. + * @throws \Exception + */ + public function getGoalFunnel($idSite, $idGoal) + { + return null; + } + + /** + * Get funnel information for this goal. + * + * @param int $idSite + * + * @return array|null Null when no funnel has been configured yet, the funnel otherwise. + * @throws \Exception + */ + public function getSalesFunnelForSite($idSite) + { + return null; + } + + /** + * Get funnel information by ID. + * + * @param int $idSite + * @param int $idFunnel The ID of the funnel for which to get data. [@example=99] + * + * @return array|null Null when no funnel has been configured yet, the funnel otherwise. + * @throws \Exception + */ + public function getFunnel(int $idSite, int $idFunnel) + { + return null; + } + + /** + * Get activated funnels for the current site. + * + * @param int $idSite + * + * @return array + */ + public function getAllActivatedFunnelsForSite($idSite) + { + return []; + } + + /** + * @param int $idSite + * + * @return bool + */ + public function hasAnyActivatedFunnelForSite($idSite) + { + return true; + } + + /** + * Deletes the given goal funnel. + * + * @param int $idSite + * @param int $idGoal The ID of the goal to which the funnel is tied. + * + * @throws \Exception + */ + public function deleteGoalFunnel($idSite, $idGoal): void + { + } + + /** + * Deletes the given goal funnel. + * + * @param int $idSite + * @param int $idFunnel + * + * @throws \Exception + */ + public function deleteNonGoalFunnel(int $idSite, int $idFunnel): void + { + } + + /** + * Sets (overwrites) a funnel for this goal. + * + * @param int $idSite + * @param int $idGoal + * @param int $isActivated Whether the funnel is activated. E.g. 0 or 1. As soon as a funnel is activated, a report + * will be generated for this funnel. + * @param array[] $steps Definitions of each funnel step. If isActivated = true, there has to be at least one step. + * E.g. [{'position': 1, 'name': 'Step1', 'pattern_type': 'path_contains', 'pattern': 'path/dir', 'required': 0}] + * + * @return int The id of the created or updated funnel + * @throws \Exception + */ + public function setGoalFunnel($idSite, $idGoal, $isActivated, $steps = []) + { + return 4; + } + + /** + * Saves a funnel not tied to a goal. + * + * @param int $idSite + * @param int $idFunnel ID of the funnel since we can't use the idSite and idGoal to identify it + * @param string $funnelName The name used to identify the funnel since it's not tied to a goal + * @param array $steps Definitions of each funnel step.Definitions of each funnel step. + * E.g. [{'position': 1, 'name': 'Step1', 'pattern_type': 'path_contains', 'pattern': 'path/dir', 'required': 0}] + * + * @return int The id of the created or updated funnel + * @throws \Exception + */ + public function saveNonGoalFunnel(int $idSite, int $idFunnel, string $funnelName, array $steps): int + { + return 4; + } + + /** + * Get a list of available pattern types that can be used to configure a funnel step. + * + * @return array + * @throws \Exception + */ + public function getAvailablePatternMatches() + { + return []; + } + + /** + * Tests whether a URL matches any of the step patterns. + * + * @param string $url A value used to filter funnel flow by. E.g. URL, path, event category, event name, page title, + * goal ID, ... [@example="https://www.example.com/path/dir"] + * @param array $steps Definitions of funnel steps. + * [@example=[{"position": 1, "name": "Step1", "pattern_type": "path_contains", "pattern": "path/dir", "required": 0}]] + * @return array + * @throws \Exception + */ + public function testUrlMatchesSteps($url, $steps) + { + $exampleResponse = [ + 'url' => 'https://www.example.com/path/dir', + 'tests' => [ + 'matches' => true, + 'pattern_type' => 'path_contains', + 'pattern' => 'path\/dir', + ], + ]; + + return []; + } +} diff --git a/tests/Unit/AnnotationGeneratorTest.php b/tests/Unit/AnnotationGeneratorTest.php index 22beaf4..854b906 100644 --- a/tests/Unit/AnnotationGeneratorTest.php +++ b/tests/Unit/AnnotationGeneratorTest.php @@ -8,10 +8,13 @@ * */ +declare(strict_types=1); + namespace Piwik\Plugins\OpenApiDocs\tests\Unit; use PHPUnit\Framework\TestCase; use Piwik\API\DocumentationGenerator; +use Piwik\API\NoDefaultValue; use Piwik\Plugins\OpenApiDocs\Annotations\AnnotationGenerator; /** @@ -21,6 +24,75 @@ */ class AnnotationGeneratorTest extends TestCase { + public const TEST_RESOURCES_DIR = __DIR__ . '/../Resources'; + + public const EXAMPLE_API_ENDPOINTS = [ + 'CustomAlerts.getAlert', + 'CustomAlerts.getAlerts', + 'CustomAlerts.getTriggeredAlerts', + 'CustomDimensions.getAvailableExtractionDimensions', + 'CustomDimensions.getAvailableScopes', + 'CustomDimensions.getConfiguredCustomDimensions', + 'CustomDimensions.getCustomDimension', + 'LogViewer.getAvailableLogReaders', + 'LogViewer.getConfiguredLogReaders', + 'LogViewer.getLogConfig', + 'LogViewer.getLogEntries', + 'MarketingCampaignsReporting.getKeyword', + 'MarketingCampaignsReporting.getName', + ]; + + public const EXAMPLE_RESPONSE_FILE_NAMES = [ + 'CustomAlerts.deleteAlert.xml', + 'CustomAlerts.getAlert.json', + 'CustomAlerts.getAlerts.json', + 'CustomAlerts.getAlerts.xml', + 'CustomAlerts.getAlert.xml', + 'CustomAlerts.getTriggeredAlerts.json', + 'CustomAlerts.getTriggeredAlerts.xml', + 'CustomDimensions.getAvailableExtractionDimensions.json', + 'CustomDimensions.getAvailableExtractionDimensions.tsv', + 'CustomDimensions.getAvailableExtractionDimensions.xml', + 'CustomDimensions.getAvailableScopes.json', + 'CustomDimensions.getAvailableScopes.tsv', + 'CustomDimensions.getAvailableScopes.xml', + 'CustomDimensions.getConfiguredCustomDimensions.json', + 'CustomDimensions.getConfiguredCustomDimensions.xml', + 'CustomDimensions.getCustomDimension.json', + 'CustomDimensions.getCustomDimension.tsv', + 'CustomDimensions.getCustomDimension.xml', + 'LogViewer.getAvailableLogReaders.json', + 'LogViewer.getAvailableLogReaders.tsv', + 'LogViewer.getAvailableLogReaders.xml', + 'LogViewer.getConfiguredLogReaders.json', + 'LogViewer.getConfiguredLogReaders.tsv', + 'LogViewer.getConfiguredLogReaders.xml', + 'LogViewer.getLogConfig.json', + 'LogViewer.getLogConfig.xml', + 'LogViewer.getLogEntries.json', + 'LogViewer.getLogEntries.tsv', + 'LogViewer.getLogEntries.xml', + 'MarketingCampaignsReporting.getKeyword.json', + 'MarketingCampaignsReporting.getKeyword.tsv', + 'MarketingCampaignsReporting.getKeyword.xml', + 'MarketingCampaignsReporting.getName.json', + 'MarketingCampaignsReporting.getName.tsv', + 'MarketingCampaignsReporting.getName.xml', + ]; + + public const EXAMPLE_API_METHOD_DOC_BLOCK1 = '/** + * Copies a specified custom report to one or more sites. If a custom report with the same name already exists, the new custom report + * will have an automatically adjusted name to make it unique to the assigned site. + * + * @param int $idSite + * @param int $idCustomReport ID of the custom report to duplicate. + * @param int[] $idDestinationSites Optional array of IDs identifying which site(s) the new custom report is to be + * assigned to. The default is [idSite] when nothing is provided. + * + * @return array + * @throws Exception + */'; + /** * @var AnnotationGenerator */ @@ -28,11 +100,440 @@ class AnnotationGeneratorTest extends TestCase public function setUp(): void { - parent::setUp(); - $this->annotationGenerator = new AnnotationGenerator(new DocumentationGenerator()); } + /** + * @param string $apiEndpoint The identifier of the endpoint, like CustomAlerts.getAlert. + * @param string $format + * + * @return string String contents of the raw response body. If the file isn't found, an empty string is returned. + * @throws \Exception + */ + private function getRawExampleResponseForApiEndpoint(string $apiEndpoint, string $format = 'json'): string + { + if (!in_array(strtolower($format), ['json', 'xml', 'tsv'])) { + throw new \Exception('Invalid format: ' . $format . '. Must be: "json", "xml", or "tsv"'); + } + + return file_get_contents(self::TEST_RESOURCES_DIR . "/ExampleResponses/{$apiEndpoint}.{$format}") ?: ''; + } + + /** + * @param string $plugin + * @param string $method + * @param string $format + * + * @return string String contents of the raw response body. If the file isn't found, an empty string is returned. + * @throws \Exception + */ + private function getRawExampleResponseForPluginMethod(string $plugin, string $method, string $format = 'json'): string + { + return $this->getRawExampleResponseForApiEndpoint("{$plugin}.{$method}", $format); + } + + /** + * Get the map of example responses. The default is returning the map for all the example responses after they've + * been normalised for schema generation, but before being truncated. + * + * @param bool $exampleResponseSchemas Return the generated schemas of the example responses. + * @param bool $onlyExamplesThatWereTruncated Return only the example responses that were truncated. + * + * @return array The map of example responses for a bunch of API endpoints. + * E.g. ['plugin.method' => ['json' => '...', 'xml' => '...', 'tsv' => '...']] + */ + private function getExampleResponsesMap(bool $exampleResponseSchemas = false, bool $onlyExamplesThatWereTruncated = false): array + { + if ($exampleResponseSchemas && $onlyExamplesThatWereTruncated) { + throw new \Exception('Only one type of example response can be returned at a time.'); + } + + if ($exampleResponseSchemas) { + $exampleResponseSchemasString = file_get_contents(self::TEST_RESOURCES_DIR . '/ExampleResponsesNormalised/ExamplesSchemasByType.json') ?: ''; + return json_decode($exampleResponseSchemasString, true) ?? []; + } + + if ($onlyExamplesThatWereTruncated) { + $exampleResponsesPostTruncationString = file_get_contents(self::TEST_RESOURCES_DIR . '/ExampleResponsesNormalised/ExamplesPostTruncationByType.json') ?: ''; + return json_decode($exampleResponsesPostTruncationString, true) ?? []; + } + + $demoExampleResponsesString = file_get_contents(self::TEST_RESOURCES_DIR . '/ExampleResponsesNormalised/ExamplesFromDemoByType.json') ?: ''; + $localExampleResponsesString = file_get_contents(self::TEST_RESOURCES_DIR . '/ExampleResponsesNormalised/ExamplesFromLocalByType.json') ?: ''; + $demoJson = json_decode($demoExampleResponsesString, true) ?? []; + $localJson = json_decode($localExampleResponsesString, true) ?? []; + return array_merge($demoJson, $localJson); + } + + public function testGeneratePluginApiAnnotations(): void + { + // TODO - Test the generatePluginApiAnnotations method + $this->expectNotToPerformAssertions(); + } + + public function testGetContentForGeneratedAnnotationsFile(): void + { + // TODO - getContentForGeneratedAnnotationsFile method + $this->expectNotToPerformAssertions(); + } + + public function testBuildAnnotationForMethod(): void + { + // TODO - buildAnnotationForMethod method + $this->expectNotToPerformAssertions(); + } + + public function testGetParamInfoFromDocBlock(): void + { + // TODO - Update to use resource file and/or dataprovider to test more than one comment block + $expected = [ + 'idSite' => [ + 'type' => 'int', + 'description' => '', + 'byRef' => false, + 'variadic' => false, + ], + 'idCustomReport' => [ + 'type' => 'int', + 'description' => 'ID of the custom report to duplicate.', + 'byRef' => false, + 'variadic' => false, + ], + 'idDestinationSites' => [ + 'type' => 'int[]', + 'description' => 'Optional array of IDs identifying which site(s) the new custom report is to be assigned to. The default is [idSite] when nothing is provided.', + 'byRef' => false, + 'variadic' => false, + ], + ]; + $this->assertEquals($expected, $this->annotationGenerator->getParamInfoFromDocBlock(self::EXAMPLE_API_METHOD_DOC_BLOCK1)); + } + + public function testGetResponseInfoFromDocBlock(): void + { + // TODO - Update to use resource file and/or dataprovider to test more than one comment block + $expected = [ + 'type' => 'array' + ]; + $this->assertEquals($expected, $this->annotationGenerator->getResponseInfoFromDocBlock(self::EXAMPLE_API_METHOD_DOC_BLOCK1)); + } + + /** + * @dataProvider getTestDataForBuildVirtualPath + * + * @param string $pathTemplate + * @param string $pluginName + * @param string $methodName + * @param string $expected + * + * @return void + */ + public function testBuildVirtualPath(string $pathTemplate, string $pluginName, string $methodName, string $expected): void + { + $this->assertEquals($expected, $this->annotationGenerator->buildVirtualPath($pathTemplate, $pluginName, $methodName)); + } + + /** + * @return iterable + */ + public function getTestDataForBuildVirtualPath(): iterable + { + yield 'should be empty when all values are empty' => ['', '', '', '']; + yield 'should be empty when template is empty' => ['', 'SomePlugin', 'SomeMethod', '']; + yield 'should remain the same when template does not include placeholders' => ['/some/test/path', 'SomePlugin', 'SomeMethod', '/some/test/path']; + yield 'should replace only plugin when the only placeholder' => ['/{plugin}/test/path', 'SomePlugin', 'SomeMethod', '/SomePlugin/test/path']; + yield 'should replace only method when the only placeholder' => ['/{method}/test/path', 'SomePlugin', 'SomeMethod', '/SomeMethod/test/path']; + yield 'should include both values when placeholders are present' => ['/{plugin}/{method}/test/path', 'SomePlugin', 'SomeMethod', '/SomePlugin/SomeMethod/test/path']; + yield 'should follow placement of placeholders' => ['/{method}/{plugin}/test/path', 'SomePlugin', 'SomeMethod', '/SomeMethod/SomePlugin/test/path']; + yield 'should allow duplication of placeholders' => ['/{plugin}/{method}/test/path/{plugin}', 'SomePlugin', 'SomeMethod', '/SomePlugin/SomeMethod/test/path/SomePlugin']; + yield 'should work with query parameter format' => ['/index.php?module=API&method={plugin}.{method}', 'SomePlugin', 'SomeMethod', '/index.php?module=API&method=SomePlugin.SomeMethod']; + yield 'should work with different names' => ['/index.php?module=API&method={plugin}.{method}', 'TagManager', 'GetContainers', '/index.php?module=API&method=TagManager.GetContainers']; + } + + /** + * @dataProvider getTestDataForBuildParameterAnnotationData + * + * @param string $paramName + * @param array $paramMetadata + * @param array $paramDocInfo + * @param array $expected + * + * @return void + */ + public function testBuildParameterAnnotationData(string $paramName, array $paramMetadata, array $paramDocInfo, array $expected): void + { + $this->assertEquals($expected, $this->annotationGenerator->buildParameterAnnotationData('someMethodName', $paramName, $paramMetadata, $paramDocInfo)); + } + + /** + * @return iterable + */ + public function getTestDataForBuildParameterAnnotationData(): iterable + { + yield 'should be default values with no data' => ['', [], [], [ + 'name' => '', + 'types' => ['string' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should be very basic with only param name' => ['someParam', [], [], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should be fine with another param name' => ['idSite', [], [], [ + 'name' => 'idSite', + 'types' => ['string' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should be still have string type when string is provided' => ['someParam', [ + 'type' => 'string', + ], [], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should be integer type when int is provided' => ['someParam', [ + 'type' => 'int', + ], [], [ + 'name' => 'someParam', + 'types' => ['integer' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should be array type when array is provided' => ['someParam', [ + 'type' => 'array', + ], [], [ + 'name' => 'someParam', + 'types' => ['array' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should show as not required and use metadata default when provided' => ['someParam', [ + 'default' => 'SomeDefaultValue', + ], [], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => '', + 'required' => 'false', + 'default' => '"SomeDefaultValue"', + 'example' => '', + ]]; + yield 'should not wrap metadata default value when boolean type' => ['someParam', [ + 'type' => 'bool', + 'default' => true, + ], [], [ + 'name' => 'someParam', + 'types' => ['boolean' => null], + 'description' => '', + 'required' => 'false', + 'default' => 'true', + 'example' => '', + ]]; + yield 'should still count false boolean as a default value' => ['someParam', [ + 'type' => 'bool', + 'default' => false, + ], [], [ + 'name' => 'someParam', + 'types' => ['boolean' => null], + 'description' => '', + 'required' => 'false', + 'default' => 'false', + 'example' => '', + ]]; + yield 'should still count empty string as a default value' => ['someParam', [ + 'default' => '', + ], [], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => '', + 'required' => 'false', + 'default' => '""', + 'example' => '', + ]]; + yield 'should not count the NoDefaultValue class as a default value' => ['someParam', [ + 'default' => new NoDefaultValue(), + ], [], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should not override the metadata type when it is integer' => ['someParam', [ + 'type' => 'int', + ], [ + 'type' => 'array', + ], [ + 'name' => 'someParam', + 'types' => ['integer' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should override the metadata type when it is string and docInfo is array' => ['someParam', [ + 'type' => 'string', + ], [ + 'type' => 'array', + ], [ + 'name' => 'someParam', + 'types' => ['array' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should use docInfo type when metadata type is empty' => ['someParam', [], [ + 'type' => 'boolean', + ], [ + 'name' => 'someParam', + 'types' => ['boolean' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should determine subtype when docInfo type indicates the type of array items' => ['someParam', [], [ + 'type' => 'int[]', + ], [ + 'name' => 'someParam', + 'types' => ['array' => 'integer'], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should still determine subtype when metadata type is array and docInfo indicates subtype' => ['someParam', [ + 'type' => 'array', + ], [ + 'type' => 'int[]', + ], [ + 'name' => 'someParam', + 'types' => ['array' => 'integer'], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should allow multiple types when docInfo includes them' => ['someParam', [], [ + 'type' => 'string|int|int[]', + ], [ + 'name' => 'someParam', + 'types' => ['string' => null, 'integer' => null, 'array' => 'integer'], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should allow multiple types when metadata type is string' => ['someParam', [ + 'type' => 'string', + ], [ + 'type' => 'string|int|int[]', + ], [ + 'name' => 'someParam', + 'types' => ['string' => null, 'integer' => null, 'array' => 'integer'], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should not allow multiple types when metadata type is specified' => ['someParam', [ + 'type' => 'integer', + ], [ + 'type' => 'string|int|int[]', + ], [ + 'name' => 'someParam', + 'types' => ['integer' => null], + 'description' => '', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should use docInfo description when provided' => ['someParam', [], [ + 'description' => 'Some test description.', + ], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => 'Some test description.', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '', + ]]; + yield 'should use example when provided using custom syntax in docInfo description' => ['someParam', [], [ + 'description' => 'Some test description. [@example=true]', + ], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => 'Some test description.', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => 'true', + ]]; + yield 'should use string example when provided using custom syntax in docInfo description' => ['someParam', [], [ + 'description' => 'Some test description. [@example="Some test example string."]', + ], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => 'Some test description.', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => 'Some test example string.', + ]]; + yield 'should allow full JSON in docInfo description examples' => ['someParam', [], [ + 'description' => 'Some test description. [@example={"key1":"value1","key2":"value2"}]', + ], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => 'Some test description.', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '{"key1":"value1","key2":"value2"}', + ]]; + yield 'should allow full JSON array in docInfo description examples' => ['someParam', [], [ + 'description' => 'Some test description. [@example=[{"key1":"value1","key2":"value2"},{"key3":"value3","key4":"value4"}]]', + ], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => 'Some test description.', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '[{"key1":"value1","key2":"value2"},{"key3":"value3","key4":"value4"}]', + ]]; + yield 'should allow full JSON array examples even when in the middle of the docInfo description' => ['someParam', [], [ + 'description' => 'Some test description. [@example=[{"key1":"value1","key2":"value2"},{"key3":"value3","key4":"value4"}]] More test description.', + ], [ + 'name' => 'someParam', + 'types' => ['string' => null], + 'description' => 'Some test description. More test description.', + 'required' => 'true', + 'default' => 'Piwik\API\NoDefaultValue', + 'example' => '[{"key1":"value1","key2":"value2"},{"key3":"value3","key4":"value4"}]', + ]]; + } + + public function testDetermineParameters(): void + { + // TODO - determineParameters method + $this->expectNotToPerformAssertions(); + } + /** * @dataProvider getTestDataForGetOpenApiTypeFromPhpType * @@ -67,4 +568,233 @@ public function getTestDataForGetOpenApiTypeFromPhpType(): iterable yield 'should be number for float' => ['float', 'number']; yield 'should be number for double' => ['double', 'number']; } + + public function testGetApplicableDemoExampleUrls(): void + { + // TODO - getApplicableDemoExampleUrls method + $this->expectNotToPerformAssertions(); + } + + public function testGetDemoReportMetadata(): void + { + // TODO - getDemoReportMetadata method + $this->expectNotToPerformAssertions(); + } + + public function testGetExampleIfAvailable(): void + { + // TODO - getExampleIfAvailable method + $this->expectNotToPerformAssertions(); + } + + public function testGetReportExampleUrlFromMetadata(): void + { + // TODO - getReportExampleUrlFromMetadata method + $this->expectNotToPerformAssertions(); + } + + public function testConvertExampleXmlToObject(): void + { + $normalisedMap = $this->getExampleResponsesMap(); + foreach (self::EXAMPLE_API_ENDPOINTS as $endpoint) { + $content = $this->getRawExampleResponseForApiEndpoint($endpoint, 'xml'); + $this->assertNotEmpty($content, 'The example response should not be empty for endpoint: ' . $endpoint); + $expected = $normalisedMap[$endpoint]['xml'] ?? []; + $this->assertEquals($expected, json_encode($this->annotationGenerator->convertExampleXmlToObject($content)), "The converted XML was not as expected for endpoint $endpoint."); + } + } + + public function testDetermineResponses(): void + { + // TODO - determineResponses method + $this->expectNotToPerformAssertions(); + } + + public function testCutExampleCloseToCharLimit(): void + { + $truncatedMap = $this->getExampleResponsesMap(false, true); + $normalisedMap = $this->getExampleResponsesMap(); + foreach (self::EXAMPLE_API_ENDPOINTS as $endpoint) { + $normalisedExamples = $normalisedMap[$endpoint] ?? []; + + foreach ($normalisedExamples as $type => $normalisedExample) { + $expectedExample = $normalisedExample; + if (!empty($truncatedMap[$endpoint][$type])) { + $expectedExample = $truncatedMap[$endpoint][$type]; + } + + // Skip the endpoints which don't have TSV examples + if ( + ( + $type === 'tsv' + && in_array($endpoint, [ + 'CustomAlerts.getAlert', + 'CustomAlerts.getAlerts', + 'CustomAlerts.getTriggeredAlerts', + 'CustomDimensions.getConfiguredCustomDimensions', + 'LogViewer.getLogConfig', + ]) + ) + ) { + continue; + } + + $this->assertNotEmpty($normalisedExample, "The example response should not be empty for endpoint '$endpoint' and type '$type'."); + $result = $this->annotationGenerator->cutExampleCloseToCharLimit($normalisedExample, $type); + $this->assertLessThanOrEqual(AnnotationGenerator::EXAMPLE_CHAR_LIMIT, strlen($result), "The example response should not exceed the character limit for endpoint '$endpoint' and type '$type'."); + $this->assertEquals($expectedExample, $result, "The truncated example was not as expected for endpoint '$endpoint' and type '$type'."); + } + } + } + + public function testBuildSchemaAnnotationFromJsonExample(): void + { + // TODO - buildSchemaAnnotationFromJsonExample method + $this->expectNotToPerformAssertions(); + } + + public function testBuildPropertyAnnotationFromJsonExample(): void + { + // TODO - buildPropertyAnnotationFromJsonExample method + $this->expectNotToPerformAssertions(); + } + + public function testBuildSchemaAnnotationFromXmlExample(): void + { + // TODO - buildSchemaAnnotationFromXmlExample method + $this->expectNotToPerformAssertions(); + } + + public function testBuildPropertyAnnotationFromXmlExample(): void + { + // TODO - buildPropertyAnnotationFromXmlExample method + $this->expectNotToPerformAssertions(); + } + + /** + * @dataProvider getTestDataForRemoveTrailingCommaFromLastLine + * + * @param array $lines + * @param array $expected + * + * @return void + */ + public function testRemoveTrailingCommaFromLastLine(array $lines, array $expected): void + { + $this->annotationGenerator->removeTrailingCommaFromLastLine($lines); + $this->assertEquals($expected, $lines); + } + + /** + * @return iterable + */ + public function getTestDataForRemoveTrailingCommaFromLastLine(): iterable + { + yield 'should be fine with empty arrays' => [[], []]; + yield 'should be fine with no commas' => [['test'], ['test']]; + yield 'should be fine with multiple lines and no commas' => [['test1', 'test2'], ['test1', 'test2']]; + yield 'should remove the trailing comma from the last line' => [['test1,', 'test2,'], ['test1,', 'test2']]; + yield 'should only remove the trailing comma from the last line' => [['test1,test2,test3,', 'test4,test5,test6,'], ['test1,test2,test3,', 'test4,test5,test6']]; + yield 'should handle spaced lists correctly' => [['test1, test2, test3,', 'test4, test5, test6,'], ['test1, test2, test3,', 'test4, test5, test6']]; + yield 'should only remove the comma if it is the last character' => [['test1, test2, test3,', 'test4, test5, test6, '], ['test1, test2, test3,', 'test4, test5, test6, ']]; + yield 'should handle nested JSON correctly' => [ + [ + '@OA\Property(', + ' property="dimensions",', + ' type="object",', + ' @OA\Property(', + ' property="row",', + ' type="array",', + ' @OA\Items(', + ' type="string"', + ' )', + ' )', + '),', + ], + [ + '@OA\Property(', + ' property="dimensions",', + ' type="object",', + ' @OA\Property(', + ' property="row",', + ' type="array",', + ' @OA\Items(', + ' type="string"', + ' )', + ' )', + ')', + ] + ]; + } + + public function testBuildLinesForAnnotationObject(): void + { + // TODO - buildLinesForAnnotationObject method + $this->expectNotToPerformAssertions(); + } + + public function testBuildSchemaObjectArray(): void + { + // TODO - buildSchemaObjectArray method + $this->expectNotToPerformAssertions(); + } + + /** + * @dataProvider getTestDataForWrapStringWithQuotes + * + * @param string $stringValue + * @param string $type + * @param string|null $quoteCharacter + * + * @return void + */ + public function testWrapStringWithQuotes(string $stringValue, string $type, ?string $quoteCharacter, string $expected): void + { + $result = $quoteCharacter === null ? $this->annotationGenerator->wrapStringWithQuotes($stringValue, $type) : $this->annotationGenerator->wrapStringWithQuotes($stringValue, $type, $quoteCharacter); + $this->assertEquals($expected, $result); + } + + /** + * @return iterable + */ + public function getTestDataForWrapStringWithQuotes(): iterable + { + yield 'should be empty quoted string if everything is empty' => ['', '', null, '""']; + yield 'should be empty quoted string if string type and empty value' => ['', 'string', null, '""']; + yield 'should be empty string if integer type and empty value' => ['', 'integer', null, '']; + yield 'should be empty string if boolean type and empty value' => ['', 'boolean', null, '']; + yield 'should be empty string if array type and empty value' => ['', 'array', null, '']; + yield 'should be empty quoted string if string type and quoted empty string value' => ['""', 'string', null, '""']; + yield 'should be empty single-quoted string if string type and single-quoted empty string value' => ["''", 'string', null, "''"]; + yield 'should be empty object string if string type and empty object value' => ['{}', 'string', null, '{}']; + yield 'should be use the quote character when provided' => ["''", '', '"', "''"]; + yield 'should be use the quote character when provided even when not quote' => ['', '', '|', '||']; + yield 'should be quoted string if no type and string value' => ['test', '', null, '"test"']; + yield 'should be quoted string if string type and string value' => ['test', 'string', null, '"test"']; + yield 'should be integer string if integer type and integer string value' => ['12', 'integer', null, '12']; + yield 'should be boolean string if boolean type and boolean string value' => ['true', 'boolean', null, 'true']; + yield 'should be array string if array type and array string value' => ['[]', 'array', null, '[]']; + yield 'should be use the custom quote character when provided even when not quote' => ['test', 'string', '|', "|test|"]; + yield 'should ignore the custom quote character when integer type' => ['30', 'integer', '|', '30']; + yield 'should ignore the custom quote character when boolean type' => ['30', 'boolean', '|', '30']; + yield 'should ignore the custom quote character when array type' => ['30', 'array', '|', '30']; + } + + public function testShouldIncludeDefault(): void + { + // TODO - shouldIncludeDefault method + $this->expectNotToPerformAssertions(); + } + + public function testBuildSchemaObjectArrays(): void + { + // TODO - buildSchemaObjectArrays method + $this->expectNotToPerformAssertions(); + } + + public function testCompileOperationLines(): void + { + // TODO - compileOperationLines method + $this->expectNotToPerformAssertions(); + } }