diff --git a/superset-frontend/spec/javascripts/sqllab/Timer_spec.jsx b/superset-frontend/spec/javascripts/sqllab/Timer_spec.jsx
index 19481679e3ea5..49efac73d89cc 100644
--- a/superset-frontend/spec/javascripts/sqllab/Timer_spec.jsx
+++ b/superset-frontend/spec/javascripts/sqllab/Timer_spec.jsx
@@ -18,6 +18,8 @@
*/
import React from 'react';
import { styledMount as mount } from 'spec/helpers/theming';
+import sinon from 'sinon';
+
import Timer from 'src/components/Timer';
import { now } from 'src/modules/dates';
@@ -38,10 +40,28 @@ describe('Timer', () => {
expect(React.isValidElement()).toBe(true);
});
- it('useEffect starts timer after 30ms and sets state of clockStr', async () => {
- expect(wrapper.find('span').text()).toBe('');
+ it('componentWillMount starts timer after 30ms and sets state.clockStr', async () => {
+ expect(wrapper.state().clockStr).toBe('');
await new Promise(r => setTimeout(r, 35));
- expect(wrapper.find('span').text()).not.toBe('');
+ expect(wrapper.state().clockStr).not.toBe('');
+ });
+
+ it('calls startTimer on mount', () => {
+ // Timer is already mounted in beforeEach
+ wrapper.unmount();
+ const startTimerSpy = sinon.spy(Timer.prototype, 'startTimer');
+ wrapper.mount();
+ // Timer is started once in willUnmount and a second timer in render
+ // TODO: Questionable whether this is necessary.
+ expect(startTimerSpy.callCount).toBe(2);
+ startTimerSpy.restore();
+ });
+
+ it('calls stopTimer on unmount', () => {
+ const stopTimerSpy = sinon.spy(Timer.prototype, 'stopTimer');
+ wrapper.unmount();
+ expect(stopTimerSpy.callCount).toBe(1);
+ stopTimerSpy.restore();
});
it('renders a span with the correct class', () => {
diff --git a/superset-frontend/src/components/Timer.jsx b/superset-frontend/src/components/Timer.jsx
new file mode 100644
index 0000000000000..5cddef3073d87
--- /dev/null
+++ b/superset-frontend/src/components/Timer.jsx
@@ -0,0 +1,95 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Label from 'src/components/Label';
+import { now, fDuration } from '../modules/dates';
+
+const propTypes = {
+ endTime: PropTypes.number,
+ isRunning: PropTypes.bool.isRequired,
+ startTime: PropTypes.number,
+ status: PropTypes.string,
+};
+
+const defaultProps = {
+ endTime: null,
+ startTime: null,
+ status: 'success',
+};
+
+export default class Timer extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ clockStr: '',
+ };
+ this.stopwatch = this.stopwatch.bind(this);
+ }
+
+ UNSAFE_componentWillMount() {
+ this.startTimer();
+ }
+
+ componentWillUnmount() {
+ this.stopTimer();
+ }
+
+ startTimer() {
+ if (!this.timer) {
+ this.timer = setInterval(this.stopwatch, 30);
+ }
+ }
+
+ stopTimer() {
+ this.timer = clearInterval(this.timer);
+ }
+
+ stopwatch() {
+ if (this.props && this.props.startTime) {
+ const endDttm = this.props.endTime || now();
+ if (this.props.startTime < endDttm) {
+ const clockStr = fDuration(this.props.startTime, endDttm);
+ this.setState({ clockStr });
+ }
+ if (!this.props.isRunning) {
+ this.stopTimer();
+ }
+ }
+ }
+
+ render() {
+ if (this.props && this.props.isRunning) {
+ this.startTimer();
+ }
+ let timerSpan = null;
+ if (this.props) {
+ timerSpan = (
+
+ );
+ }
+ return timerSpan;
+ }
+}
+
+Timer.propTypes = propTypes;
+Timer.defaultProps = defaultProps;
diff --git a/superset-frontend/src/components/Timer.tsx b/superset-frontend/src/components/Timer.tsx
deleted file mode 100644
index 7daedef31b8a6..0000000000000
--- a/superset-frontend/src/components/Timer.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import React, { useEffect, useState } from 'react';
-import Label from 'src/components/Label';
-
-import { now, fDuration } from '../modules/dates';
-
-interface TimerProps {
- endTime?: number;
- isRunning: boolean;
- startTime?: number;
- status?: string;
-}
-
-export default function Timer({
- endTime,
- isRunning,
- startTime,
- status = 'success',
-}: TimerProps) {
- const [clockStr, setClockStr] = useState('');
- const [timer, setTimer] = useState();
-
- const stopTimer = () => {
- if (timer) {
- clearInterval(timer);
- setTimer(undefined);
- }
- };
-
- const stopwatch = () => {
- if (startTime) {
- const endDttm = endTime || now();
- if (startTime < endDttm) {
- setClockStr(fDuration(startTime, endDttm));
- }
- if (!isRunning) {
- stopTimer();
- }
- }
- };
-
- const startTimer = () => {
- setTimer(setInterval(stopwatch, 30));
- };
-
- useEffect(() => {
- if (isRunning) {
- startTimer();
- }
- }, [isRunning]);
-
- useEffect(() => {
- return () => {
- stopTimer();
- };
- });
-
- return (
-
- );
-}