Compare commits

..

14 Commits

Author SHA1 Message Date
Andrew Henry
d8c85bf858 Example code for image thumbnail formatter 2023-01-13 16:49:26 -08:00
Even Stensberg
cdb202d8ba tooling(webpack): move webpack to its own folder (#6076) 2023-01-12 11:46:35 -08:00
dependabot[bot]
905373f294 Bump sass-loader from 13.0.2 to 13.2.0 (#5968)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 13.0.2 to 13.2.0.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v13.0.2...v13.2.0)

---
updated-dependencies:
- dependency-name: sass-loader
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 16:57:54 -08:00
dependabot[bot]
60c07ab506 Bump sass from 1.56.1 to 1.57.1 (#6068)
Bumps [sass](https://github.com/sass/dart-sass) from 1.56.1 to 1.57.1.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.56.1...1.57.1)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rukmini Bose (Ruki) <48999852+rukmini-bose@users.noreply.github.com>
2023-01-09 16:45:50 -08:00
dependabot[bot]
7336abc111 Bump css-loader from 6.7.1 to 6.7.3 (#6056)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 6.7.1 to 6.7.3.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v6.7.1...v6.7.3)

---
updated-dependencies:
- dependency-name: css-loader
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 16:34:24 -08:00
dependabot[bot]
8fe9da89a3 Bump eslint from 8.30.0 to 8.31.0 (#6091)
Bumps [eslint](https://github.com/eslint/eslint) from 8.30.0 to 8.31.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.30.0...v8.31.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 08:56:14 -08:00
dependabot[bot]
e6bdaa957a Bump plotly.js-basic-dist from 2.14.0 to 2.17.0 (#6078)
Bumps [plotly.js-basic-dist](https://github.com/plotly/plotly.js) from 2.14.0 to 2.17.0.
- [Release notes](https://github.com/plotly/plotly.js/releases)
- [Changelog](https://github.com/plotly/plotly.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.js/compare/v2.14.0...v2.17.0)

---
updated-dependencies:
- dependency-name: plotly.js-basic-dist
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-03 16:27:54 -08:00
dependabot[bot]
93b5519c4b Bump karma-spec-reporter from 0.0.34 to 0.0.36 (#6058)
Bumps [karma-spec-reporter](https://github.com/tmcgee123/karma-spec-reporter) from 0.0.34 to 0.0.36.
- [Release notes](https://github.com/tmcgee123/karma-spec-reporter/releases)
- [Commits](https://github.com/tmcgee123/karma-spec-reporter/compare/v0.0.34...v0.0.36)

---
updated-dependencies:
- dependency-name: karma-spec-reporter
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-03 13:32:19 -08:00
Jesse Mazzella
04ef4b369c chore: bump version to 2.1.6-SNAPSHOT (#6092) 2023-01-03 09:50:28 -08:00
Jamie V
5424a62db5 [Notebook] Handle conflicts properly (#6067)
* making a revert on failed save more clear

* only notify conflicts for non sync items in object api, spruce up notebook with better transaction tracking and observing and unobserving during transactions, structuredClone backup in monkeypatch

* WIP

* WIP debuggin

* fresh start

* dont observe in transaction objects, small changes to notebook vue to indicate saving/prevent spamming, added forceRemote flag to objects.get

* updating readability of code as well as fix issue of stuck transaction for same value entry edits

* once entry is created, click out to blur

* quick revert
;

* click outside of entry to blur and commit

* switched to enter... as suggested :)

* removing unused variable

* initializing transaction to null as we are using that now for no transaction

* fix: ensure EventSource is closed so it recovers

- Make sure to close the CouchDB EventSource as well, so that it can recover in the case where two tabs or windows are on Open MCT and one refreshes. The check on line 81 was preventing recovery since the EventSource was not closed properly.

* enhance, enhance, enhance readability

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2022-12-29 14:11:08 -08:00
Shefali Joshi
9ed9e62202 Use the current clock's timestamp to show the now line in the timestrip (#6082) 2022-12-28 22:18:47 +00:00
Jesse Mazzella
327fc826c1 fix(imagery): Unblock 'latest' strategy requests for Related Telemetry in realtime mode (#6080)
* fix: use ephemeral timeContext for thumbnail metadata requests

* fix(TEMP): use `eval-source-map`

- **!!! REVERT THIS CHANGE BEFORE MERGE !!!**

* fix: only mutate if object supports mutation

* fix: pass identifier instead of whole domainObject

* fix: add start and end bounds to request

* Revert "fix(TEMP): use `eval-source-map`"

This reverts commit 7972d8c33a.

* docs: add comments
2022-12-28 19:12:00 +00:00
Jesse Mazzella
a9e3eca35c chore: bump Playwright to v1.29 (#6004)
* chore: bump Playwright to 1.28.0

* chore: bump playwright to v1.29.0

* fix: remove `|| true` shim for codecov

* Revert "fix: remove `|| true` shim for codecov"

This reverts commit ca3766fb5a.

* docs: add instructions for upgrading Playwright
2022-12-27 14:46:19 -08:00
Andrew Henry
cbecd79f71 Do not register time system listener until we have resolve remote clock object (#6063) 2022-12-20 14:01:47 -08:00
22 changed files with 395 additions and 263 deletions

View File

@@ -2,7 +2,7 @@ version: 2.1
executors:
pw-focal-development:
docker:
- image: mcr.microsoft.com/playwright:v1.25.2-focal
- image: mcr.microsoft.com/playwright:v1.29.0-focal
environment:
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps

View File

@@ -23,7 +23,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: '16'
- run: npx playwright@1.25.2 install
- run: npx playwright@1.29.0 install
- run: npm install
- run: sh src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh
- run: npm run test:e2e:couchdb

View File

@@ -30,7 +30,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: '16'
- run: npx playwright@1.25.2 install
- run: npx playwright@1.29.0 install
- run: npx playwright install chrome-beta
- run: npm install
- run: npm run test:e2e:full

176
.webpack/webpack.common.js Normal file
View File

@@ -0,0 +1,176 @@
/* global __dirname module */
/*
This is the OpenMCT common webpack file. It is imported by the other three webpack configurations:
- webpack.prod.js - the production configuration for OpenMCT (default)
- webpack.dev.js - the development configuration for OpenMCT
- webpack.coverage.js - imports webpack.dev.js and adds code coverage
There are separate npm scripts to use these configurations, though simply running `npm install`
will use the default production configuration.
*/
const path = require("path");
const packageDefinition = require("../package.json");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { VueLoaderPlugin } = require("vue-loader");
let gitRevision = "error-retrieving-revision";
let gitBranch = "error-retrieving-branch";
try {
gitRevision = require("child_process")
.execSync("git rev-parse HEAD")
.toString()
.trim();
gitBranch = require("child_process")
.execSync("git rev-parse --abbrev-ref HEAD")
.toString()
.trim();
} catch (err) {
console.warn(err);
}
/** @type {import('webpack').Configuration} */
const config = {
context: path.join(__dirname, ".."),
entry: {
openmct: "./openmct.js",
generatorWorker: "./example/generator/generatorWorker.js",
couchDBChangesFeed:
"./src/plugins/persistence/couch/CouchChangesFeed.js",
inMemorySearchWorker: "./src/api/objects/InMemorySearchWorker.js",
espressoTheme: "./src/plugins/themes/espresso-theme.scss",
snowTheme: "./src/plugins/themes/snow-theme.scss"
},
output: {
globalObject: "this",
filename: "[name].js",
path: path.resolve(__dirname, "..", "dist"),
library: "openmct",
libraryTarget: "umd",
publicPath: "",
hashFunction: "xxhash64",
clean: true
},
resolve: {
alias: {
"@": path.join(__dirname, "..", "src"),
legacyRegistry: path.join(__dirname, "..", "src/legacyRegistry"),
saveAs: "file-saver/src/FileSaver.js",
csv: "comma-separated-values",
EventEmitter: "eventemitter3",
bourbon: "bourbon.scss",
"plotly-basic": "plotly.js-basic-dist",
"plotly-gl2d": "plotly.js-gl2d-dist",
"d3-scale": path.join(
__dirname,
"..",
"node_modules/d3-scale/dist/d3-scale.min.js"
),
printj: path.join(
__dirname,
"..",
"node_modules/printj/dist/printj.min.js"
),
styles: path.join(__dirname, "..", "src/styles"),
MCT: path.join(__dirname, "..", "src/MCT"),
testUtils: path.join(__dirname, "..", "src/utils/testUtils.js"),
objectUtils: path.join(
__dirname,
"..",
"src/api/objects/object-utils.js"
),
utils: path.join(__dirname, "..", "src/utils")
}
},
plugins: [
new webpack.DefinePlugin({
__OPENMCT_VERSION__: `'${packageDefinition.version}'`,
__OPENMCT_BUILD_DATE__: `'${new Date()}'`,
__OPENMCT_REVISION__: `'${gitRevision}'`,
__OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`
}),
new VueLoaderPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: "src/images/favicons",
to: "favicons"
},
{
from: "./index.html",
transform: function (content) {
return content.toString().replace(/dist\//g, "");
}
},
{
from: "src/plugins/imagery/layers",
to: "imagery"
}
]
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[name].css"
})
],
module: {
rules: [
{
test: /\.(sc|sa|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader"
},
{
loader: "resolve-url-loader"
},
{
loader: "sass-loader",
options: { sourceMap: true }
}
]
},
{
test: /\.vue$/,
use: "vue-loader"
},
{
test: /\.html$/,
type: "asset/source"
},
{
test: /\.(jpg|jpeg|png|svg)$/,
type: "asset/resource",
generator: {
filename: "images/[name][ext]"
}
},
{
test: /\.ico$/,
type: "asset/resource",
generator: {
filename: "icons/[name][ext]"
}
},
{
test: /\.(woff|woff2?|eot|ttf)$/,
type: "asset/resource",
generator: {
filename: "fonts/[name][ext]"
}
}
]
},
stats: "errors-warnings",
performance: {
// We should eventually consider chunking to decrease
// these values
maxEntrypointSize: 25000000,
maxAssetSize: 25000000
}
};
module.exports = config;

View File

@@ -6,9 +6,9 @@ OpenMCT Continuous Integration servers use this configuration to add code covera
information to pull requests.
*/
const config = require('./webpack.dev');
const config = require("./webpack.dev");
// eslint-disable-next-line no-undef
const CI = process.env.CI === 'true';
const CI = process.env.CI === "true";
config.devtool = CI ? false : undefined;
@@ -18,13 +18,18 @@ config.module.rules.push({
test: /\.js$/,
exclude: /(Spec\.js$)|(node_modules)/,
use: {
loader: 'babel-loader',
loader: "babel-loader",
options: {
retainLines: true,
// eslint-disable-next-line no-undef
plugins: [['babel-plugin-istanbul', {
extension: ['.js', '.vue']
}]]
plugins: [
[
"babel-plugin-istanbul",
{
extension: [".js", ".vue"]
}
]
]
}
}
});

View File

@@ -5,28 +5,28 @@ This configuration should be used for development purposes. It contains full sou
devServer (which be invoked using by `npm start`), and a non-minified Vue.js distribution.
If OpenMCT is to be used for a production server, use webpack.prod.js instead.
*/
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
const path = require('path');
const webpack = require('webpack');
const path = require("path");
const webpack = require("webpack");
module.exports = merge(common, {
mode: 'development',
mode: "development",
watchOptions: {
// Since we use require.context, webpack is watching the entire directory.
// We need to exclude any files we don't want webpack to watch.
// See: https://webpack.js.org/configuration/watch/#watchoptions-exclude
ignored: [
'**/{node_modules,dist,docs,e2e}', // All files in node_modules, dist, docs, e2e,
'**/{*.yml,Procfile,webpack*.js,babel*.js,package*.json,tsconfig.json}', // Config files
'**/*.{sh,md,png,ttf,woff,svg}', // Non source files
'**/.*' // dotfiles and dotfolders
"**/{node_modules,dist,docs,e2e}", // All files in node_modules, dist, docs, e2e,
"**/{*.yml,Procfile,webpack*.js,babel*.js,package*.json,tsconfig.json}", // Config files
"**/*.{sh,md,png,ttf,woff,svg}", // Non source files
"**/.*" // dotfiles and dotfolders
]
},
resolve: {
alias: {
"vue": path.join(__dirname, "node_modules/vue/dist/vue.js")
vue: path.join(__dirname, "..", "node_modules/vue/dist/vue.js")
}
},
plugins: [
@@ -34,20 +34,20 @@ module.exports = merge(common, {
__OPENMCT_ROOT_RELATIVE__: '"dist/"'
})
],
devtool: 'eval-source-map',
devtool: "eval-source-map",
devServer: {
devMiddleware: {
writeToDisk: (filePathString) => {
const filePath = path.parse(filePathString);
const shouldWrite = !(filePath.base.includes('hot-update'));
const shouldWrite = !filePath.base.includes("hot-update");
return shouldWrite;
}
},
watchFiles: ['**/*.css'],
watchFiles: ["**/*.css"],
static: {
directory: path.join(__dirname, '/dist'),
publicPath: '/dist',
directory: path.join(__dirname, "..", "/dist"),
publicPath: "/dist",
watch: false
},
client: {

View File

@@ -4,17 +4,17 @@
This configuration should be used for production installs.
It is the default webpack configuration.
*/
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
const path = require('path');
const webpack = require('webpack');
const path = require("path");
const webpack = require("webpack");
module.exports = merge(common, {
mode: 'production',
mode: "production",
resolve: {
alias: {
"vue": path.join(__dirname, "node_modules/vue/dist/vue.min.js")
vue: path.join(__dirname, "..", "node_modules/vue/dist/vue.min.js")
}
},
plugins: [
@@ -22,5 +22,5 @@ module.exports = merge(common, {
__OPENMCT_ROOT_RELATIVE__: '""'
})
],
devtool: 'source-map'
devtool: "source-map"
});

View File

@@ -8,7 +8,7 @@ This document is designed to capture on the What, Why, and How's of writing and
1. [Getting Started](#getting-started)
2. [Types of Testing](#types-of-e2e-testing)
3. [Architecture](#architecture)
3. [Architecture](#test-architecture-and-ci)
## Getting Started
@@ -400,3 +400,23 @@ A single e2e test in Open MCT is extended to run:
- Tests won't start because 'Error: <http://localhost:8080/># is already used...'
This error will appear when running the tests locally. Sometimes, the webserver is left in an orphaned state and needs to be cleaned up. To clear up the orphaned webserver, execute the following from your Terminal:
```lsof -n -i4TCP:8080 | awk '{print$2}' | tail -1 | xargs kill -9```
### Upgrading Playwright
In order to upgrade from one version of Playwright to another, the version should be updated in several places in both `openmct` and `openmct-yamcs` repos. An easy way to identify these locations is to search for the current version in all files and find/replace.
For reference, all of the locations where the version should be updated are listed below:
#### **In `openmct`:**
- `package.json`
- Both packages `@playwright/test` and `playwright-core` should be updated to the same target version.
- `.circleci/config.yml`
- `.github/workflows/e2e-couchdb.yml`
- `.github/workflows/e2e-pr.yml`
#### **In `openmct-yamcs`:**
- `package.json`
- `@playwright/test` should be updated to the target version.
- `.github/workflows/yamcs-quickstart-e2e.yml`

View File

@@ -44,6 +44,7 @@ async function createNotebookAndEntry(page, iterations = 1) {
const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = ${iteration}`;
await page.locator(entryLocator).click();
await page.locator(entryLocator).fill(`Entry ${iteration}`);
await page.locator(entryLocator).press('Enter');
}
return notebook;

View File

@@ -28,12 +28,12 @@ module.exports = (config) => {
let singleRun;
if (process.env.KARMA_DEBUG) {
webpackConfig = require('./webpack.dev.js');
browsers = ['ChromeDebugging'];
webpackConfig = require("./.webpack/webpack.dev.js");
browsers = ["ChromeDebugging"];
singleRun = false;
} else {
webpackConfig = require('./webpack.coverage.js');
browsers = ['ChromeHeadless'];
webpackConfig = require("./.webpack/webpack.coverage.js");
browsers = ["ChromeHeadless"];
singleRun = true;
}
@@ -42,28 +42,28 @@ module.exports = (config) => {
delete webpackConfig.entry;
config.set({
basePath: '',
frameworks: ['jasmine', 'webpack'],
basePath: "",
frameworks: ["jasmine", "webpack"],
files: [
'indexTest.js',
"indexTest.js",
// included means: should the files be included in the browser using <script> tag?
// We don't want them as a <script> because the shared worker source
// needs loaded remotely by the shared worker process.
{
pattern: 'dist/couchDBChangesFeed.js*',
pattern: "dist/couchDBChangesFeed.js*",
included: false
},
{
pattern: 'dist/inMemorySearchWorker.js*',
pattern: "dist/inMemorySearchWorker.js*",
included: false
},
{
pattern: 'dist/generatorWorker.js*',
pattern: "dist/generatorWorker.js*",
included: false
}
],
port: 9876,
reporters: ['spec', 'junit', 'coverage-istanbul'],
reporters: ["spec", "junit", "coverage-istanbul"],
browsers,
client: {
jasmine: {
@@ -73,8 +73,8 @@ module.exports = (config) => {
},
customLaunchers: {
ChromeDebugging: {
base: 'Chrome',
flags: ['--remote-debugging-port=9222'],
base: "Chrome",
flags: ["--remote-debugging-port=9222"],
debug: true
}
},
@@ -90,7 +90,7 @@ module.exports = (config) => {
fixWebpackSourcePaths: true,
skipFilesWithNoCoverage: true,
dir: "coverage/unit", //Sets coverage file to be consumed by codecov.io
reports: ['lcovonly']
reports: ["lcovonly"]
},
specReporter: {
maxLogLines: 5,
@@ -102,11 +102,11 @@ module.exports = (config) => {
failFast: false
},
preprocessors: {
'indexTest.js': ['webpack', 'sourcemap']
"indexTest.js": ["webpack", "sourcemap"]
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-warnings'
stats: "errors-warnings"
},
concurrency: 1,
singleRun,

View File

@@ -1,13 +1,13 @@
{
"name": "openmct",
"version": "2.1.5-SNAPSHOT",
"version": "2.1.6-SNAPSHOT",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.18.9",
"@braintree/sanitize-url": "6.0.2",
"@percy/cli": "1.16.0",
"@percy/playwright": "1.0.4",
"@playwright/test": "1.25.2",
"@playwright/test": "1.29.0",
"@types/jasmine": "4.3.1",
"@types/lodash": "4.14.191",
"babel-loader": "9.1.0",
@@ -15,11 +15,11 @@
"codecov": "3.8.3",
"comma-separated-values": "3.6.4",
"copy-webpack-plugin": "11.0.0",
"css-loader": "6.7.1",
"css-loader": "6.7.3",
"d3-axis": "3.0.0",
"d3-scale": "3.3.0",
"d3-selection": "3.0.0",
"eslint": "8.30.0",
"eslint": "8.31.0",
"eslint-plugin-compat": "4.0.2",
"eslint-plugin-playwright": "0.11.2",
"eslint-plugin-vue": "9.8.0",
@@ -38,7 +38,7 @@
"karma-jasmine": "5.1.0",
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.34",
"karma-spec-reporter": "0.0.36",
"karma-webpack": "5.0.0",
"location-bar": "3.0.1",
"lodash": "4.17.21",
@@ -48,13 +48,13 @@
"moment-timezone": "0.5.40",
"nyc": "15.1.0",
"painterro": "1.2.78",
"playwright-core": "1.25.2",
"plotly.js-basic-dist": "2.14.0",
"playwright-core": "1.29.0",
"plotly.js-basic-dist": "2.17.0",
"plotly.js-gl2d-dist": "2.14.0",
"printj": "1.3.1",
"resolve-url-loader": "5.0.0",
"sass": "1.56.1",
"sass-loader": "13.0.2",
"sass": "1.57.1",
"sass-loader": "13.2.0",
"sinon": "15.0.1",
"style-loader": "^3.3.1",
"typescript": "4.9.4",
@@ -71,14 +71,14 @@
"scripts": {
"clean": "rm -rf ./dist ./node_modules ./package-lock.json",
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
"start": "npx webpack serve --config ./webpack.dev.js",
"start:coverage": "npx webpack serve --config ./webpack.coverage.js",
"start": "npx webpack serve --config ./.webpack/webpack.dev.js",
"start:coverage": "npx webpack serve --config ./.webpack/webpack.coverage.js",
"lint": "eslint example src e2e --ext .js,.vue openmct.js --max-warnings=0",
"lint:fix": "eslint example src e2e --ext .js,.vue openmct.js --fix",
"build:prod": "webpack --config webpack.prod.js",
"build:dev": "webpack --config webpack.dev.js",
"build:coverage": "webpack --config webpack.coverage.js",
"build:watch": "webpack --config webpack.dev.js --watch",
"build:prod": "webpack --config ./.webpack/webpack.prod.js",
"build:dev": "webpack --config ./.webpack/webpack.dev.js",
"build:coverage": "webpack --config ./.webpack/webpack.coverage.js",
"build:watch": "webpack --config ./.webpack/webpack.dev.js --watch",
"info": "npx envinfo --system --browsers --npmPackages --binaries --languages --markdown",
"test": "karma start",
"test:debug": "KARMA_DEBUG=true karma start",

View File

@@ -193,23 +193,27 @@ export default class ObjectAPI {
* @memberof module:openmct.ObjectProvider#
* @param {string} key the key for the domain object to load
* @param {AbortController.signal} abortSignal (optional) signal to abort fetch requests
* @param {boolean} forceRemote defaults to false. If true, will skip cached and
* dirty/in-transaction objects use and the provider.get method
* @returns {Promise} a promise which will resolve when the domain object
* has been saved, or be rejected if it cannot be saved
*/
get(identifier, abortSignal) {
get(identifier, abortSignal, forceRemote = false) {
let keystring = this.makeKeyString(identifier);
if (this.cache[keystring] !== undefined) {
return this.cache[keystring];
}
if (!forceRemote) {
if (this.cache[keystring] !== undefined) {
return this.cache[keystring];
}
identifier = utils.parseKeyString(identifier);
identifier = utils.parseKeyString(identifier);
if (this.isTransactionActive()) {
let dirtyObject = this.transaction.getDirtyObject(identifier);
if (this.isTransactionActive()) {
let dirtyObject = this.transaction.getDirtyObject(identifier);
if (dirtyObject) {
return Promise.resolve(dirtyObject);
if (dirtyObject) {
return Promise.resolve(dirtyObject);
}
}
}
@@ -391,7 +395,6 @@ export default class ObjectAPI {
lastPersistedTime = domainObject.persisted;
const persistedTime = Date.now();
this.#mutate(domainObject, 'persisted', persistedTime);
savedObjectPromise = provider.update(domainObject);
}
@@ -399,7 +402,7 @@ export default class ObjectAPI {
savedObjectPromise.then(response => {
savedResolve(response);
}).catch((error) => {
if (lastPersistedTime !== undefined) {
if (!isNewObject) {
this.#mutate(domainObject, 'persisted', lastPersistedTime);
}
@@ -412,11 +415,12 @@ export default class ObjectAPI {
return result.catch(async (error) => {
if (error instanceof this.errors.Conflict) {
this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
// Synchronized objects will resolve their own conflicts
if (this.SYNCHRONIZED_OBJECT_TYPES.includes(domainObject.type)) {
this.openmct.notifications.info(`Conflict detected while saving "${this.makeKeyString(domainObject.name)}", attempting to resolve`);
} else {
this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
// Synchronized objects will resolve their own conflicts, so
// bypass the refresh here and throw the error.
if (!this.SYNCHRONIZED_OBJECT_TYPES.includes(domainObject.type)) {
if (this.isTransactionActive()) {
this.endTransaction();
}

View File

@@ -788,7 +788,7 @@ export default {
}
},
persistVisibleLayers() {
if (this.domainObject.configuration) {
if (this.domainObject.configuration && this.openmct.objects.supportsMutation(this.domainObject.identifier)) {
this.openmct.objects.mutate(this.domainObject, 'configuration.layers', this.layers);
}

View File

@@ -28,6 +28,7 @@ function copyRelatedMetadata(metadata) {
return copiedMetadata;
}
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
export default class RelatedTelemetry {
constructor(openmct, domainObject, telemetryKeys) {
@@ -88,9 +89,31 @@ export default class RelatedTelemetry {
this[key].historicalDomainObject = await this._openmct.objects.get(this[key].historical.telemetryObjectId);
this[key].requestLatestFor = async (datum) => {
const options = {
// We need to create a throwaway time context and pass it along
// as a request option. We do this to "trick" the Time API
// into thinking we are in fixed time mode in order to bypass this logic:
// https://github.com/akhenry/openmct-yamcs/blob/1060d42ebe43bf346dac0f6a8068cb288ade4ba4/src/providers/historical-telemetry-provider.js#L59
// Context: https://github.com/akhenry/openmct-yamcs/pull/217
const ephemeralContext = new IndependentTimeContext(
this._openmct,
this._openmct.time,
[this[key].historicalDomainObject]
);
// Stop following the global context, stop the clock,
// and set bounds.
ephemeralContext.resetContext();
const newBounds = {
start: this._openmct.time.bounds().start,
end: this._parseTime(datum),
end: this._parseTime(datum)
};
ephemeralContext.stopClock();
ephemeralContext.bounds(newBounds);
const options = {
start: newBounds.start,
end: newBounds.end,
timeContext: ephemeralContext,
strategy: 'latest'
};
let results = await this._openmct.telemetry

View File

@@ -35,9 +35,11 @@ export default {
// set
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
this.imageMetadataValue = { ...this.metadata.valuesForHints(['image'])[0] };
this.imageThumbnailMetadataValue = { ...this.metadata.valuesForHints(['thumbnail'])[0] };
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageMetadataValue);
this.imageThumbnailFormatter = this.openmct.telemetry.getValueFormatter(this.imageThumbnailMetadataValue);
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
// initialize

View File

@@ -50,7 +50,7 @@
<Sidebar
ref="sidebar"
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
:class="sidebarClasses"
:default-page-id="defaultPageId"
:selected-page-id="getSelectedPageId()"
:default-section-id="defaultSectionId"
@@ -123,6 +123,7 @@
</div>
<div
v-if="selectedPage && !selectedPage.isLocked"
:class="{ 'disabled': activeTransaction }"
class="c-notebook__drag-area icon-plus"
@click="newEntry()"
@dragover="dragOver"
@@ -133,6 +134,11 @@
To start a new entry, click here or drag and drop any object
</span>
</div>
<progress-bar
v-if="savingTransaction"
class="c-telemetry-table__progress-bar"
:model="{ progressPerc: undefined }"
/>
<div
v-if="selectedPage && selectedPage.isLocked"
class="c-notebook__page-locked"
@@ -183,6 +189,7 @@ import NotebookEntry from './NotebookEntry.vue';
import Search from '@/ui/components/search.vue';
import SearchResults from './SearchResults.vue';
import Sidebar from './Sidebar.vue';
import ProgressBar from '../../../ui/components/ProgressBar.vue';
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSectionId, setDefaultNotebookPageId } from '../utils/notebook-storage';
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
import { saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from '../utils/notebook-image';
@@ -200,7 +207,8 @@ export default {
NotebookEntry,
Search,
SearchResults,
Sidebar
Sidebar,
ProgressBar
},
inject: ['agent', 'openmct', 'snapshotContainer'],
props: {
@@ -225,7 +233,9 @@ export default {
showNav: false,
sidebarCoversEntries: false,
filteredAndSortedEntries: [],
notebookAnnotations: {}
notebookAnnotations: {},
activeTransaction: false,
savingTransaction: false
};
},
computed: {
@@ -270,6 +280,20 @@ export default {
return this.sections[0];
},
sidebarClasses() {
let sidebarClasses = [];
if (this.showNav) {
sidebarClasses.push('is-expanded');
}
if (this.sidebarCoversEntries) {
sidebarClasses.push('c-drawer--overlays');
} else {
sidebarClasses.push('c-drawer--push');
}
return sidebarClasses;
},
showLockButton() {
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
@@ -297,6 +321,8 @@ export default {
this.formatSidebar();
this.setSectionAndPageFromUrl();
this.transaction = null;
window.addEventListener('orientationchange', this.formatSidebar);
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
this.filterAndSortEntries();
@@ -749,6 +775,7 @@ export default {
return section.id;
},
async newEntry(embed = null) {
this.startTransaction();
this.resetSearch();
const notebookStorage = this.createNotebookStorageObject();
this.updateDefaultNotebook(notebookStorage);
@@ -891,20 +918,34 @@ export default {
},
startTransaction() {
if (!this.openmct.objects.isTransactionActive()) {
this.activeTransaction = true;
this.transaction = this.openmct.objects.startTransaction();
}
},
async saveTransaction() {
if (this.transaction !== undefined) {
await this.transaction.commit();
this.openmct.objects.endTransaction();
if (this.transaction !== null) {
this.savingTransaction = true;
try {
await this.transaction.commit();
} finally {
this.endTransaction();
}
}
},
async cancelTransaction() {
if (this.transaction !== undefined) {
await this.transaction.cancel();
this.openmct.objects.endTransaction();
if (this.transaction !== null) {
try {
await this.transaction.cancel();
} finally {
this.endTransaction();
}
}
},
endTransaction() {
this.openmct.objects.endTransaction();
this.transaction = null;
this.savingTransaction = false;
this.activeTransaction = false;
}
}
};

View File

@@ -74,19 +74,22 @@ async function resolveNotebookTagConflicts(localAnnotation, openmct) {
async function resolveNotebookEntryConflicts(localMutable, openmct) {
if (localMutable.configuration.entries) {
const FORCE_REMOTE = true;
const localEntries = structuredClone(localMutable.configuration.entries);
const remoteMutable = await openmct.objects.getMutable(localMutable.identifier);
applyLocalEntries(remoteMutable, localEntries, openmct);
openmct.objects.destroyMutable(remoteMutable);
const remoteObject = await openmct.objects.get(localMutable.identifier, undefined, FORCE_REMOTE);
return applyLocalEntries(remoteObject, localEntries, openmct);
}
return true;
}
function applyLocalEntries(mutable, entries, openmct) {
function applyLocalEntries(remoteObject, entries, openmct) {
let shouldSave = false;
Object.entries(entries).forEach(([sectionKey, pagesInSection]) => {
Object.entries(pagesInSection).forEach(([pageKey, localEntries]) => {
const remoteEntries = mutable.configuration.entries[sectionKey][pageKey];
const remoteEntries = remoteObject.configuration.entries[sectionKey][pageKey];
const mergedEntries = [].concat(remoteEntries);
let shouldMutate = false;
@@ -110,8 +113,13 @@ function applyLocalEntries(mutable, entries, openmct) {
});
if (shouldMutate) {
openmct.objects.mutate(mutable, `configuration.entries.${sectionKey}.${pageKey}`, mergedEntries);
shouldSave = true;
openmct.objects.mutate(remoteObject, `configuration.entries.${sectionKey}.${pageKey}`, mergedEntries);
}
});
});
if (shouldSave) {
return openmct.objects.save(remoteObject);
}
}

View File

@@ -36,8 +36,8 @@ export default function () {
}
let wrappedFunction = openmct.objects.get;
openmct.objects.get = function migrate(identifier) {
return wrappedFunction.apply(openmct.objects, [identifier])
openmct.objects.get = function migrate() {
return wrappedFunction.apply(openmct.objects, [...arguments])
.then(function (object) {
if (needsMigration(object)) {
migrateObject(object)

View File

@@ -28,6 +28,7 @@
connected = false;
// stop listening for events
couchEventSource.removeEventListener('message', self.onCouchMessage);
couchEventSource.close();
console.debug('🚪 Closed couch connection 🚪');
return;

View File

@@ -96,8 +96,13 @@ class CouchObjectProvider {
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
//TODO: Optimize this so that we don't 'get' the object if it's current revision (from this.objectQueue) is the same as the one we already have.
let observersForObject = this.observers[keyString];
let isInTransaction = false;
if (observersForObject) {
if (this.openmct.objects.isTransactionActive()) {
isInTransaction = this.openmct.objects.transaction.getDirtyObject(objectIdentifier);
}
if (observersForObject && !isInTransaction) {
observersForObject.forEach(async (observer) => {
const updatedObject = await this.get(objectIdentifier);
if (this.isSynchronizedObject(updatedObject)) {
@@ -219,7 +224,12 @@ class CouchObjectProvider {
console.error(error.message);
throw new Error(`CouchDB Error - No response"`);
} else {
console.error(error.message);
if (body?.model && isNotebookOrAnnotationType(body.model)) {
// warn since we handle conflicts for notebooks
console.warn(error.message);
} else {
console.error(error.message);
}
throw error;
}

View File

@@ -101,7 +101,8 @@ export default {
if (nowMarker) {
nowMarker.classList.remove('hidden');
nowMarker.style.height = this.contentHeight + 'px';
const now = this.xScale(Date.now());
const nowTimeStamp = this.openmct.time.clock().currentValue();
const now = this.xScale(nowTimeStamp);
nowMarker.style.left = now + this.offset + 'px';
}
}

View File

@@ -1,160 +0,0 @@
/* global __dirname module */
/*
This is the OpenMCT common webpack file. It is imported by the other three webpack configurations:
- webpack.prod.js - the production configuration for OpenMCT (default)
- webpack.dev.js - the development configuration for OpenMCT
- webpack.coverage.js - imports webpack.dev.js and adds code coverage
There are separate npm scripts to use these configurations, though simply running `npm install`
will use the default production configuration.
*/
const path = require('path');
const packageDefinition = require('./package.json');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const {VueLoaderPlugin} = require('vue-loader');
let gitRevision = 'error-retrieving-revision';
let gitBranch = 'error-retrieving-branch';
try {
gitRevision = require('child_process')
.execSync('git rev-parse HEAD')
.toString().trim();
gitBranch = require('child_process')
.execSync('git rev-parse --abbrev-ref HEAD')
.toString().trim();
} catch (err) {
console.warn(err);
}
/** @type {import('webpack').Configuration} */
const config = {
entry: {
openmct: './openmct.js',
generatorWorker: './example/generator/generatorWorker.js',
couchDBChangesFeed: './src/plugins/persistence/couch/CouchChangesFeed.js',
inMemorySearchWorker: './src/api/objects/InMemorySearchWorker.js',
espressoTheme: './src/plugins/themes/espresso-theme.scss',
snowTheme: './src/plugins/themes/snow-theme.scss',
},
output: {
globalObject: 'this',
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
library: 'openmct',
libraryTarget: 'umd',
publicPath: '',
hashFunction: 'xxhash64',
clean: true
},
resolve: {
alias: {
"@": path.join(__dirname, "src"),
"legacyRegistry": path.join(__dirname, "src/legacyRegistry"),
"saveAs": "file-saver/src/FileSaver.js",
"csv": "comma-separated-values",
"EventEmitter": "eventemitter3",
"bourbon": "bourbon.scss",
"plotly-basic": "plotly.js-basic-dist",
"plotly-gl2d": "plotly.js-gl2d-dist",
"d3-scale": path.join(__dirname, "node_modules/d3-scale/dist/d3-scale.min.js"),
"printj": path.join(__dirname, "node_modules/printj/dist/printj.min.js"),
"styles": path.join(__dirname, "src/styles"),
"MCT": path.join(__dirname, "src/MCT"),
"testUtils": path.join(__dirname, "src/utils/testUtils.js"),
"objectUtils": path.join(__dirname, "src/api/objects/object-utils.js"),
"utils": path.join(__dirname, "src/utils")
}
},
plugins: [
new webpack.DefinePlugin({
__OPENMCT_VERSION__: `'${packageDefinition.version}'`,
__OPENMCT_BUILD_DATE__: `'${new Date()}'`,
__OPENMCT_REVISION__: `'${gitRevision}'`,
__OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`
}),
new VueLoaderPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: 'src/images/favicons',
to: 'favicons'
},
{
from: './index.html',
transform: function (content) {
return content.toString().replace(/dist\//g, '');
}
},
{
from: 'src/plugins/imagery/layers',
to: 'imagery'
}
]
}),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].css'
})
],
module: {
rules: [
{
test: /\.(sc|sa|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
},
{
loader: 'resolve-url-loader'
},
{
loader: 'sass-loader',
options: {sourceMap: true }
}
]
},
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.html$/,
type: 'asset/source'
},
{
test: /\.(jpg|jpeg|png|svg)$/,
type: 'asset/resource',
generator: {
filename: 'images/[name][ext]'
}
},
{
test: /\.ico$/,
type: 'asset/resource',
generator: {
filename: 'icons/[name][ext]'
}
},
{
test: /\.(woff|woff2?|eot|ttf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[name][ext]'
}
}
]
},
stats: 'errors-warnings',
performance: {
// We should eventually consider chunking to decrease
// these values
maxEntrypointSize: 25000000,
maxAssetSize: 25000000
}
};
module.exports = config;