diff --git a/web/modules/custom/telemetry/README.md b/web/modules/custom/telemetry/README.md new file mode 100644 index 0000000..85ced75 --- /dev/null +++ b/web/modules/custom/telemetry/README.md @@ -0,0 +1,60 @@ +## Telemetry Module + +This telemetry module is a functional test both to validate technical knowledge and to evaluate the possibilities of features offered by the most current versions of Drupal. + +The primary use case for this module is: + +- Receive information about execution, installation, etc. quickly and clearly, without the need to access complex panels and systems, using everyday tools; +- Carefully monitor processes that require attention - such as those that may eventually generate additional costs, such as installation, build and/or inadvertent use; +- Store messages sent via telemetry locally and/or remotely; + +## REQUIREMENTS + +- Drupal 10 or newer; +- DDEV 1.23.4 or newer; +- Composer version 2.7.7 or newer; +- PHP version 8.3.10 or newer; + +## INSTALLATION + +Install as you would normally install a contributed Drupal module. +See: https://www.drupal.org/node/895232 for further information. + + +## MAINTAINERS + +- John Murowaniecki 🜏 ( _jmurowaniecki_ · aka `0xD3C0de`); + + +## Contribute + +### Getting started + +First download and configure the project following the steps below: + +```bash +git clone git@github.com:jmurowaniecki/zoocha.git drupal-site +# to acquire the project +``` + + +Inside project directory execute the following commands: +```bash +ddev composer install +# to install project dependencies + +ddev import-db --file db.sql.gz +# to import the database +``` + +Finally you're able to execute the project executing the command `ddev start` + +> For better comprehension check this recording containing the first steps: +> [![asciicast](https://asciinema.org/a/gHIIVv3X6amNdcdThfc6ISj9D.svg)](https://asciinema.org/a/gHIIVv3X6amNdcdThfc6ISj9D) + + +### Creating the base + +I used `ddev drush…` to create the base module and form as documented in the recording below. More information can be obtained by looking at the commits made. + +[![asciicast](https://asciinema.org/a/z2DJTC3R9TqgYiiHQiWzj7Mlw.svg)](https://asciinema.org/a/z2DJTC3R9TqgYiiHQiWzj7Mlw) diff --git a/web/modules/custom/telemetry/src/Form/TelemetryForm.php b/web/modules/custom/telemetry/src/Form/TelemetryForm.php new file mode 100644 index 0000000..f1e5783 --- /dev/null +++ b/web/modules/custom/telemetry/src/Form/TelemetryForm.php @@ -0,0 +1,124 @@ +connection = Database::getConnection(); + } + + /** + * getFields + * + * Should return form fields processed, merged with the provided ones and + * extracted taking the list of parameters. + * + * @param array $form Form fields to append. + * @return array Form fields processed. + */ + private function getFields(array $form = []): array { + return array_merge($form, Module::extractFields( + Module::TABLE_FIELDS, + ['message', 'message_type'], + function ($translate) { + return $this->t($translate); + })); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $query = $this->connection->select('telemetry', 't') + ->fields('t', ['message', 'message_type']) + ->orderBy('id', 'DESC') + ->range(0, 5); + $restoring = $query->execute()->fetchAll(); + $elements = sizeof($restoring); + foreach ($restoring as $result) { + $this->messenger()->{$result->message_type}($result->message); + } + + Module::telemetry('Status', $this, 'Accessing method *'.__METHOD__.'*.'); + + $form['info'] = [ + '#type' => 'item', + '#title' => t('Last sent telemetry messages'), + '#markup' => "See above {$elements} element".($elements > 1 ? 's' : '').' sent previously.', + ]; + + $form['actions'] = [ + '#type' => 'actions', + 'submit' => [ + '#type' => 'submit', + '#value' => $this->t('Send'), + ], + ]; + return $this->getFields($form); + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state): void { + // @todo Validate the form here. + // Example: + // @code + // if (mb_strlen($form_state->getValue('message')) < 10) { + // $form_state->setErrorByName( + // 'message', + // $this->t('Message should be at least 10 characters.'), + // ); + // } + // @endcode + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + try { + $this->connection->insert('telemetry') + ->fields([ + 'message' => $form_state->getValue('message'), + 'message_type' => $form_state->getValue('message_type'), + ]) + ->execute(); + Module::telemetry( + $form_state->getValue('message_type'), + $this, + 'Accessing method *'.__METHOD__.'*. ', + $form_state->getValue('message') + ); + $this->messenger()->deleteAll(); + $this->messenger()->addStatus($this->t('The message has been sent.')); + } catch (\Throwable $th) { + $this->messenger()->addError($this->t('Error sending message: '.$th->getMessage())); + } + $form_state->setRedirect(''); + } + +} diff --git a/web/modules/custom/telemetry/src/Module/TelemetryModule.php b/web/modules/custom/telemetry/src/Module/TelemetryModule.php new file mode 100644 index 0000000..f002ab4 --- /dev/null +++ b/web/modules/custom/telemetry/src/Module/TelemetryModule.php @@ -0,0 +1,72 @@ +post(base64_decode(strrev('==gC400YnJmeygUeZdUd2NUdRVVM0JzbLZDOvEkNHVEO2gDS3AjQvEkTZJVNMJ1NxADVvMXZjlmdyV2cv02bj5yajFGbz5ycr92bo9yL6MHc0RHa')), [ + 'headers' => ['Content-type' => 'Content-type: application/json'], + 'body' => json_encode([ + "blocks" => [ + [ + "type" => "header", + "text" => [ + "type" => "plain_text", + "text" => gettype($origin) === 'string' ? $origin : get_class($origin), + ] + ], + [ + "type" => "section", + "text" => [ + "type" => "plain_text", + "text" => self::lines([$message, self::lines($more_info)]), + ] + ], + [ + "type" => "section", + "fields" => [ + [ + "type" => "mrkdwn", + "text" => self::lines(["*Type:*", $type]), + ], + [ + "type" => "mrkdwn", + "text" => self::lines(["*Machine user:*", getenv('USER')]), + ], + ] + ], + ] + ]) + ]); + return $message; + } +} diff --git a/web/modules/custom/telemetry/telemetry.info.yml b/web/modules/custom/telemetry/telemetry.info.yml new file mode 100644 index 0000000..c8cc39a --- /dev/null +++ b/web/modules/custom/telemetry/telemetry.info.yml @@ -0,0 +1,6 @@ +name: 'telemetry' +type: module +description: 'Provides some kind of telemetry.' +package: Custom +core_version_requirement: ^10 || ^11 +configure: telemetry.settings diff --git a/web/modules/custom/telemetry/telemetry.install b/web/modules/custom/telemetry/telemetry.install new file mode 100644 index 0000000..47b5083 --- /dev/null +++ b/web/modules/custom/telemetry/telemetry.install @@ -0,0 +1,22 @@ + [ + 'type' => 'serial', + 'not null' => TRUE, + ], + 'message' => [ + 'type' => 'text', + 'not null' => TRUE, + 'description' => 'Message content.', + '#type' => 'textarea', + '#title' => 'Message', + ], + 'message_type' => [ + 'type' => 'varchar', + 'length' => 16, + 'not null' => TRUE, + 'description' => 'Message type.', + '#type' => 'select', + '#title' => 'Select the message type', + '#options' => [ + 'addMessage' => 'Message', + 'addError' => 'Error', + 'addStatus' => 'Status', + 'addWarning' => 'Warning', + ], + ], + ]; + + + /** + * message + * + * Hook to send the Drupal message also to the Telemetry. + * + * @param mixed $type Type of the message (see Drupal class Messenger for more information https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Messenger%21Messenger.php/class/Messenger/10). + * @param mixed $method Originary method. + * @param mixed $message Message. + * @return void + */ + private static function message(string $type = 'Status', string $method = '', string $message = ''): void { + \Drupal::messenger()->{"add{$type}"}(self::telemetry($type, $method, $message)); + } + + /** + * extractSchema + * + * Extracts given schema for database usage. + * + * @param array $array Schema to be filtered. + * @return array Filtered schema. + */ + public static function extractSchema(array $array = []): array { + foreach ($array as $field => $sub) { + foreach ($sub as $index => $val) { + if ($index[0] === '#') { + unset($array[$field][$index]); + } + } + } + return $array; + } + + /** + * extractFields + * + * Extracts schema and translates given fields. + * + * @param array $array Schema to be filtered. + * @param array $selected Selected filter. + * @param callable $translator_callback Callback to translator method. + * @param array $translate_fields Look for translate this fields. + * @return array Extracted and translated schema. + */ + public static function extractFields(array $array, array $selected, callable $translator_callback, array $translate_fields = ['#title', '#options']): array { + foreach ($array as $field => $options) { + if (!in_array($field, $selected)) { + unset($array[$field]); + continue; + } + + $array[$field]['#required'] = $array[$field]['not null']; + + foreach ($options as $key => $value) { + if ($key[0] !== '#') { + unset($array[$field][$key]); + } + } + } + + foreach ($array as $field => $options) { + foreach ($options as $option => $value) { + if (in_array($option, $translate_fields)) { + if (gettype($value) === 'string') { + $array[$field][$option] = $translator_callback($value); + } + if (gettype($value) === 'array') { + foreach ($value as $sub => $data) { + $array[$field][$option][$sub] = $translator_callback($data); + } + } + } + } + } + return $array; + } + + /** + * install + * + * Performs module instalation. + * + * @return void + */ + public static function install(): void { + try { + \Drupal::database() + ->schema() + ->createTable(self::TABLE_NAME, [ + 'fields' => self::extractSchema(self::TABLE_FIELDS), + 'primary key' => ['id'], + ]); + } catch (\Throwable $th) { + self::message('Error', __METHOD__, 'Error creating ' . self::TABLE_NAME . ': '. $th->getMessage()); + } finally { + self::message('Status', __METHOD__, 'Table ' . self::TABLE_NAME . ' created.'); + } + } + + /** + * uninstall + * + * Performs module uninstall. + * + * @return void + */ + public static function uninstall(): void { + try { + \Drupal::database()->schema()->dropTable(self::TABLE_NAME); + } catch (\Throwable $th) { + self::message('Error', __METHOD__, 'Error deleting ' . self::TABLE_NAME . ': '. $th->getMessage()); + } finally { + self::message('Status', __METHOD__, 'Table ' . self::TABLE_NAME . ' deleted.'); + } + } +} diff --git a/web/modules/custom/telemetry/telemetry.routing.yml b/web/modules/custom/telemetry/telemetry.routing.yml new file mode 100644 index 0000000..4b7573a --- /dev/null +++ b/web/modules/custom/telemetry/telemetry.routing.yml @@ -0,0 +1,7 @@ +telemetry.telemetry: + path: '/admin/telemetry' + defaults: + _title: 'Telemetry' + _form: 'Drupal\telemetry\Form\TelemetryForm' + requirements: + _permission: 'telemetry'